Enhance tool change handling in GCode: Introduce support for local-z unplanned tool changes in the WipeTowerIntegration class. Update tool_change method to accommodate an additional parameter for local_z_unplanned, allowing for more flexible tool change management during Local-Z phase-b operations. Adjust related logic in process_layer to ensure proper synchronization with wipe tower planning.

This commit is contained in:
Rad
2026-02-17 19:14:18 +01:00
parent 77d0e1b2d7
commit 9bba530cc2
2 changed files with 108 additions and 5 deletions

View File

@@ -922,10 +922,79 @@ std::string WipeTowerIntegration::prime(GCode& gcodegen)
return gcode;
}
std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer)
std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer, bool local_z_unplanned)
{
std::string gcode;
auto emit_local_z_unplanned_toolchange = [&]() -> std::string {
if (extruder_id < 0 || !gcodegen.writer().need_toolchange(extruder_id))
return "";
auto pick_toolchange_template_from_layer =
[&](int layer_idx) -> const WipeTower::ToolChangeResult* {
if (layer_idx < 0 || layer_idx >= int(m_tool_changes.size()))
return nullptr;
const auto& layer_changes = m_tool_changes[layer_idx];
for (const WipeTower::ToolChangeResult& candidate : layer_changes) {
if (candidate.initial_tool != candidate.new_tool)
return &candidate;
}
return nullptr;
};
// Prefer a real toolchange template (initial_tool != new_tool). Using "empty grid"
// / finish-layer templates here can suppress [change_filament_gcode] injection and
// produce no actual emitted T-change.
const WipeTower::ToolChangeResult* template_tcr = nullptr;
if (m_layer_idx >= 0 && m_layer_idx < int(m_tool_changes.size()))
template_tcr = pick_toolchange_template_from_layer(m_layer_idx);
if (template_tcr == nullptr) {
const int start_back = std::min<int>(m_layer_idx - 1, int(m_tool_changes.size()) - 1);
for (int back = start_back; back >= 0; --back) {
template_tcr = pick_toolchange_template_from_layer(back);
if (template_tcr != nullptr)
break;
}
}
if (template_tcr == nullptr) {
const int start_fwd = std::max<int>(m_layer_idx + 1, 0);
for (int fwd = start_fwd; fwd < int(m_tool_changes.size()); ++fwd) {
template_tcr = pick_toolchange_template_from_layer(fwd);
if (template_tcr != nullptr)
break;
}
}
if (template_tcr == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "Wipe tower local-z unplanned toolchange fallback unavailable (no real template),"
<< " using raw toolchange"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id;
return gcodegen.set_extruder(unsigned(extruder_id),
gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
}
WipeTower::ToolChangeResult tcr = *template_tcr;
tcr.initial_tool = gcodegen.writer().extruder() ? int(gcodegen.writer().extruder()->id()) : extruder_id;
tcr.new_tool = extruder_id;
tcr.print_z = float(gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
tcr.priming = false;
tcr.force_travel = true;
BOOST_LOG_TRIVIAL(debug) << "Wipe tower local-z unplanned toolchange emitted"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id
<< " tool_change_idx=" << m_tool_change_idx;
std::string out;
out += "; local-z unplanned wipe-tower toolchange begin\n";
out += append_tcr2(gcodegen, tcr, extruder_id);
out += "; local-z unplanned wipe-tower toolchange end\n";
return out;
};
if (local_z_unplanned)
return emit_local_z_unplanned_toolchange();
assert(m_layer_idx >= 0);
if (m_layer_idx >= (int) m_tool_changes.size())
return gcode;
@@ -4568,7 +4637,9 @@ LayerResult GCode::process_layer(const Print& print,
std::vector<LocalZPassBucket> pass_buckets;
};
const bool local_z_perimeter_runtime_supported = !has_wipe_tower && !is_anything_overridden;
// Local-Z phase-b can run with wipe tower enabled, but wiping-overrides remain unsupported
// because that flow emits a separate purge/print pass that is not Local-Z aware yet.
const bool local_z_perimeter_runtime_supported = !is_anything_overridden;
bool local_z_phase_b_requested_for_layer = false;
std::vector<LocalZLayerContext> local_z_layer_contexts;
std::vector<std::unique_ptr<ExtrusionEntityCollection>> local_z_clipped_collections;
@@ -4718,7 +4789,7 @@ LayerResult GCode::process_layer(const Print& print,
<< " print_z=" << print_z
<< " wipe_tower=" << has_wipe_tower
<< " wiping_overrides=" << is_anything_overridden;
gcode += "; local-z perimeter phase-b disabled for this layer (wipe tower or wiping overrides active)\n";
gcode += "; local-z perimeter phase-b disabled for this layer (wiping overrides active)\n";
} else if (local_z_phase_b_requested_for_layer && !local_z_perimeter_phase_b_enabled) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z phase-b requested but no eligible contexts"
<< " print_z=" << print_z
@@ -5119,6 +5190,9 @@ LayerResult GCode::process_layer(const Print& print,
}
if (!local_z_pass_refs.empty()) {
const int local_z_phase_b_start_extruder =
(has_wipe_tower && m_writer.extruder() != nullptr) ? int(m_writer.extruder()->id()) : -1;
bool local_z_phase_b_changed_extruder = false;
BOOST_LOG_TRIVIAL(info) << "Local-Z phase-b emitting"
<< " print_z=" << print_z
<< " perimeter_passes=" << local_z_pass_refs.size();
@@ -5152,7 +5226,13 @@ LayerResult GCode::process_layer(const Print& print,
if (objects_by_extruder.empty())
continue;
gcode += this->set_extruder(local_extruder_id, pass_plan.print_z);
if (has_wipe_tower && m_writer.need_toolchange(local_extruder_id))
local_z_phase_b_changed_extruder = true;
if (has_wipe_tower && m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, int(local_extruder_id), false, true);
} else {
gcode += this->set_extruder(local_extruder_id, pass_plan.print_z);
}
if (std::abs(m_writer.get_position().z() - pass_z) > EPSILON) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z pass z restore"
<< " print_z=" << print_z
@@ -5200,6 +5280,27 @@ LayerResult GCode::process_layer(const Print& print,
m_last_layer_z = saved_last_layer_z;
}
// Keep wipe tower planning synchronized with the normal per-layer extruder loop:
// Local-Z may do extra toolchanges, but wipe tower was planned against the layer loop order.
// Restore the pre-pass tool so wipe tower tool_change() consumes the expected sequence.
if (has_wipe_tower && local_z_phase_b_changed_extruder) {
if (local_z_phase_b_start_extruder >= 0 &&
m_writer.need_toolchange(static_cast<unsigned int>(local_z_phase_b_start_extruder))) {
BOOST_LOG_TRIVIAL(debug) << "Local-Z phase-b restoring pre-pass extruder for wipe tower"
<< " print_z=" << print_z
<< " restore_extruder=" << local_z_phase_b_start_extruder;
gcode += "; local-z phase-b restore pre-pass extruder for wipe tower\n";
if (m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, local_z_phase_b_start_extruder, false, true);
} else {
gcode += this->set_extruder(static_cast<unsigned int>(local_z_phase_b_start_extruder), print_z);
}
} else if (local_z_phase_b_start_extruder < 0) {
BOOST_LOG_TRIVIAL(warning) << "Local-Z phase-b cannot restore pre-pass extruder (undefined writer state)"
<< " print_z=" << print_z;
}
}
const double nominal_layer_z = print_z + m_config.z_offset.value;
if (std::abs(m_writer.get_position().z() - nominal_layer_z) > EPSILON) {
gcode += this->retract(false, false, LiftType::NormalLift);

View File

@@ -98,7 +98,9 @@ public:
std::string prime(GCode &gcodegen);
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
// If local_z_unplanned is true, emit a wipe/toolchange without consuming the preplanned
// per-layer wipe-tower sequence (used by Local-Z phase-b extra toolchanges).
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, bool local_z_unplanned = false);
bool is_empty_wipe_tower_gcode(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
std::vector<float> used_filament_length() const;