fix(GCodeProcessor): exclude end gcode M400 delays from M73 time estimation

End gcode contains firmware-conditional M400 waits for air purification,
timelapse capture, and sound notification that are post-print operations.
These were incorrectly included in M73 total time, inflating the estimate.

The fix detects MACHINE_END_GCODE_START tag during the streaming parse
(process_tags) and sets m_skip_end_gcode_delays=true. process_M400 then
skips timed delays (S/P params) in the end gcode scope.

BBS achieves the same effect by dropping leftover in calculate_time
(is_final=true). We skip at the source instead, which is more surgical
and leaves calculate_time behavior unchanged for all printers.

Affects all BBL printers with MACHINE_END_GCODE_START tag.
Non-BBL printers are unaffected (no tag = no skip).
This commit is contained in:
denis svinarchuk
2026-07-16 20:47:49 +01:00
parent fc705612a4
commit 403292cb63
2 changed files with 49 additions and 1 deletions

View File

@@ -614,6 +614,8 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
float leftover = 0.0f;
for (size_t i = additional_buffer_idx; i < additional_buffer.size(); ++i)
leftover += additional_buffer[i].second;
BOOST_LOG_TRIVIAL(debug) << "calculate_time(is_final): leftover=" << leftover
<< "s from " << (additional_buffer.size() - additional_buffer_idx) << " items";
time += double(leftover);
gcode_time.cache += leftover;
} else {
@@ -1527,7 +1529,15 @@ void GCodeProcessor::run_post_process()
tag.remove_suffix(1);
tag.remove_prefix(1); // strip leading ';'
if (tag == Machine_Start_GCode_End_Tag) { m_machine_start_gcode_end_line_id = line_id; return; }
if (tag == Machine_End_GCode_Start_Tag) { m_machine_end_gcode_start_line_id = line_id; return; }
if (tag == Machine_End_GCode_Start_Tag) {
m_machine_end_gcode_start_line_id = line_id;
// End gcode M400 delays (air purification, timelapse, sound) are post-print
// by definition. Skip them so M73 reports print completion time.
// Reference to BBS: BambuStudio/src/libslic3r/GCode/GCodeProcessor.cpp —
// BBS drops leftover in calculate_time(is_final=true), achieving the same.
m_skip_end_gcode_delays = true;
return;
}
}
// filament-change commands: T<fid>, ;VT<fid>, M1020 S<fid>, each optionally " H<nozzle_id>"
@@ -2922,6 +2932,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_preheat_steps = 1;
m_result.backtrace_enabled = config.ooze_prevention && m_preheat_time > 0 && (m_is_XL_printer || (!m_single_extruder_multi_material && filament_count > 1));
m_support_air_filtration = config.support_air_filtration.value;
assert(config.nozzle_volume.size() == config.nozzle_diameter.size());
m_nozzle_volume.resize(config.nozzle_volume.size());
for (size_t idx = 0; idx < config.nozzle_volume.size(); ++idx)
@@ -3075,6 +3087,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionInt *nozzle_HRC = config.option<ConfigOptionInt>("nozzle_hrc");
if (nozzle_HRC != nullptr) m_result.nozzle_hrc = nozzle_HRC->value;
const ConfigOptionBool *support_air_filt = config.option<ConfigOptionBool>("support_air_filtration");
if (support_air_filt != nullptr)
m_support_air_filtration = support_air_filt->value;
const ConfigOptionInts* physical_extruder_map = config.option<ConfigOptionInts>("physical_extruder_map");
if (physical_extruder_map != nullptr) {
m_physical_extruder_map = physical_extruder_map->values;
@@ -3492,6 +3508,8 @@ void GCodeProcessor::reset()
m_extruder_blocks.clear();
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
m_machine_end_gcode_start_line_id = (unsigned int) (-1);
m_support_air_filtration = false;
m_skip_end_gcode_delays = false;
m_remaining_volume = std::vector<float>(MAXIMUM_EXTRUDER_NUMBER, 0.f);
m_line_id = 0;
@@ -4219,6 +4237,15 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
return;
}
// End gcode marker: skip M400 S/P delays after this point so M73 reports
// print completion time, not post-print filtration/cooldown time.
// Reference to BBS: BambuStudio/src/libslic3r/GCode/GCodeProcessor.cpp —
// BBS drops leftover in calculate_time(is_final=true), achieving the same.
if (comment == Machine_End_GCode_Start_Tag) {
m_skip_end_gcode_delays = true;
return;
}
// Orca: Integrate filament consumption for purging performed to an external device and controlled via macros
// (eg. Happy Hare) in the filament consumption stats.
if (boost::starts_with(comment, GCodeProcessor::External_Purge_Tag)) {
@@ -6401,6 +6428,18 @@ void GCodeProcessor::process_M400(const GCodeReader::GCodeLine& line)
float value_p = 0.0;
if (line.has_value('S', value_s) || line.has_value('P', value_p)) {
value_s += value_p * 0.001;
// End gcode M400 delays (air purification M400 S180, timelapse M400 S5,
// sound M400 S1) are post-print by definition. Skip them so M73 reports
// print completion time, not post-print filtration/cooldown time.
// Reference to BBS: BambuStudio/src/libslic3r/GCode/GCodeProcessor.cpp —
// BBS drops leftover in calculate_time(is_final=true), achieving the same
// effect of excluding end gcode delays from M73 total. We skip at the
// source instead, leaving calculate_time unchanged for all printers.
if (m_skip_end_gcode_delays)
return;
simulate_st_synchronize(value_s);
}
}

View File

@@ -1109,6 +1109,15 @@ class Print;
std::vector<ExtruderPreHeating::ExtruderUsageBlcok> m_extruder_blocks;
unsigned int m_machine_start_gcode_end_line_id{ (unsigned int) (-1) };
unsigned int m_machine_end_gcode_start_line_id{ (unsigned int) (-1) };
// End gcode M400 delays (air purification, timelapse, sound) are post-print
// by definition. M73 should report print completion time, not post-print time.
// m_skip_end_gcode_delays is set when MACHINE_END_GCODE_START tag is encountered
// during the streaming parse (process_tags), telling process_M400 to skip timed
// delays in the end gcode scope.
// Reference to BBS: BambuStudio/src/libslic3r/GCode/GCodeProcessor.cpp —
// BBS drops leftover in calculate_time(is_final=true), achieving the same effect.
bool m_support_air_filtration{ false };
bool m_skip_end_gcode_delays{ false };
// Tracks, during the stream, which filament sits in each physical nozzle and which nozzle each
// extruder currently carries. Written by both branches of the two-arg process_filament_change
// (the fallback branch does occupancy bookkeeping only); read by the richer change-time model