diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 63c7d8d24f..e223f20920 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -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(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(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 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 local_z_layer_contexts; std::vector> 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(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(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); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index fc1298f4fe..03708b8e4c 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -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 used_filament_length() const;