Fix total_toolchanges placeholder being 0 without a wipe tower (#13895)

* fix: populate total_toolchanges without a wipe tower

total_toolchanges is documented as available while change_filament_gcode (and the wipe-tower toolchange flow) is evaluated, but it was sourced only from WipeTowerData::number_of_toolchanges, which stays -1 (clamped to 0) when no wipe tower is generated. Manual filament swaps and toolchanger/IDEX setups without a wipe tower therefore always saw total_toolchanges = 0 in custom G-code and output filenames, despite real tool changes occurring -- breaking the placeholder's documented contract.

Add a tool-ordering fallback: when number_of_toolchanges < 0, count tool changes from the print's tool ordering (the transitions in the per-layer extruder sequence). Wipe-tower prints are untouched -- number_of_toolchanges >= 0 still wins -- so their reported count does not change.

Limitation: sequential (by-object) prints without a wipe tower leave Print::tool_ordering() empty, so total_toolchanges stays 0 there (unchanged from before).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mrmees
2026-05-28 09:09:15 -05:00
committed by GitHub
parent b598df8b3b
commit 4f4683cf9c

View File

@@ -2278,17 +2278,45 @@ namespace DoExport {
ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
}
// Count tool/filament changes across the print from the tool ordering. Used as a fallback when no
// wipe tower populated WipeTowerData::number_of_toolchanges (left at -1). Covers non-sequential
// prints without a wipe tower (manual swaps, toolchanger/IDEX). Note: sequential (by-object) prints
// leave print.tool_ordering() empty, so total_toolchanges stays 0 there (unchanged from before).
static int total_toolchanges_from_ordering(const ToolOrdering &tool_ordering)
{
int changes = 0;
int last = -1;
for (const LayerTools &lt : tool_ordering)
for (unsigned int extruder : lt.extruders) {
if (last >= 0 && int(extruder) != last)
++ changes;
last = int(extruder);
}
return changes;
}
// Total tool changes for the print, preferring the wipe-tower count and falling back to the tool
// ordering when no wipe tower populated it (number_of_toolchanges < 0).
static int resolve_total_toolchanges(const WipeTowerData &wipe_tower_data, const ToolOrdering &tool_ordering)
{
int changes = wipe_tower_data.number_of_toolchanges;
if (changes < 0)
changes = total_toolchanges_from_ordering(tool_ordering);
return std::max(0, changes);
}
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
static std::string update_print_stats_and_format_filament_stats(
const bool has_wipe_tower,
const WipeTowerData &wipe_tower_data,
const std::vector<Extruder> &extruders,
PrintStatistics &print_statistics)
PrintStatistics &print_statistics,
const ToolOrdering &tool_ordering)
{
std::string filament_stats_string_out;
print_statistics.clear();
print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
print_statistics.total_toolchanges = resolve_total_toolchanges(wipe_tower_data, tool_ordering);
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
@@ -2858,7 +2886,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
this->placeholder_parser().set("has_wipe_tower", has_wipe_tower);
this->placeholder_parser().set("has_single_extruder_multi_material_priming", wipe_tower_type == WipeTowerType::Type2 && has_wipe_tower && print.config().single_extruder_multi_material_priming);
this->placeholder_parser().set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
this->placeholder_parser().set("total_toolchanges", DoExport::resolve_total_toolchanges(print.wipe_tower_data(), print.tool_ordering()));
this->placeholder_parser().set("num_extruders", int(print.config().nozzle_diameter.values.size()));
this->placeholder_parser().set("retract_length", new ConfigOptionFloats(print.config().retraction_length));
@@ -3473,7 +3501,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
has_wipe_tower, print.wipe_tower_data(),
m_writer.extruders(),
// Modifies
print.m_print_statistics));
print.m_print_statistics,
// Const input (tool-change fallback for non-wipe-tower prints)
print.tool_ordering()));
print.m_print_statistics.initial_tool = initial_extruder_id;
if (!is_bbl_printers) {
file.write_format("; total filament used [g] = %.2lf\n",