From 086bbf986be86fa612d0ca86b1672f1a25cdca6e Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 29 Jul 2026 19:59:33 +0800 Subject: [PATCH] Restart the WipeTower2 wipe at the box boundary after multitool ramming With the gap wall on a multi-tool printer, quantize the ram band up to its whole reserved rows (as the BBL tower does for the old-tool purge) and start CP TOOLCHANGE WIPE at the left-edge boundary on a fresh row below it instead of continuing from wherever the ram serpentine ended. The entry scrub then runs at the wall gap on ram toolchanges too, and the wipe box is whole rows, so it is filled completely like the no-ram case. SEMM and skip-points-off behavior is unchanged. --- src/libslic3r/GCode/WipeTower2.cpp | 69 +++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index c032a1f2c2..b846302abb 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -1671,11 +1671,13 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. - // Without a ram the box was planned as whole wipe rows; the wipe then fills it - // completely so adjacent purge blocks stay contiguous. Uses the old tool - // (m_current_tool before toolchange_Change), same condition as - // toolchange_Unload()'s do_ramming. - const bool fill_box = !((m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming); + // Without a ram — or with the boundary wipe start, where the ram band is + // quantized to whole rows — the box is planned as whole wipe rows; the wipe + // then fills it completely so adjacent purge blocks stay contiguous. Uses the + // old tool (m_current_tool before toolchange_Change), same conditions as + // toolchange_Unload()'s do_ramming / boundary_wipe_start. + const bool do_ram_old = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming; + const bool fill_box = !do_ram_old || (!m_semm && m_use_gap_wall); auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature; toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, (is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature), @@ -1756,6 +1758,12 @@ void WipeTower2::toolchange_Unload( // Orca: Do ramming when SEMM and ramming is enabled or when multi tool head when ramming is enabled on the multi tool. const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming; const bool cold_ramming = m_is_mk4mmu3; + // Orca: see set_toolchange() — quantized ram band + wipe restart at the boundary. + const bool boundary_wipe_start = do_ramming && !m_semm && m_use_gap_wall; + float planned_ramming_depth = 0.f; + if (boundary_wipe_start && m_layer_info != m_plan.end()) + for (const auto& tch : m_layer_info->tool_changes) + if (tch.old_tool == m_current_tool) { planned_ramming_depth = tch.ramming_depth; break; } if (do_ramming) { writer.travel(ramming_start_pos); // move to starting position @@ -1768,7 +1776,8 @@ void WipeTower2::toolchange_Unload( writer.set_position(ramming_start_pos); // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: - if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { + // (with a boundary wipe start the band is quantized to whole rows below, so no phase alignment is needed) + if (do_ramming && !boundary_wipe_start && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { // this is y of the center of previous sparse infill border float sparse_beginning_y = 0.f; @@ -1827,6 +1836,28 @@ void WipeTower2::toolchange_Unload( e_done = 0; } } + + // Orca: quantize the ram band up to the whole reserved rows (BBL quantizes the + // old-tool purge the same way) so no unprinted void is left between the band and + // the wipe restarting at the boundary below it. + if (planned_ramming_depth > 0.f) { + const int reserved_rows = std::max(1, int(std::round(planned_ramming_depth / y_step))); + const float last_row_y = ramming_start_pos.y() + (reserved_rows - 1) * y_step; + // Same bead model as the ramming segments above: E per mm of ram line. + const float e_per_mm = 1.f / (volume_to_length(1.f, line_width, m_layer_height) * filament_area()); + const float fill_feed = m_filpar[m_current_tool].ramming_speed.empty() ? 3000.f : + 60.f * volume_to_length(m_filpar[m_current_tool].ramming_speed.back(), line_width, m_layer_height); + while (true) { + const float target_x = m_left_to_right ? xr : xl; + if (std::abs(target_x - writer.x()) > WT_EPSILON) + writer.ram(writer.x(), target_x, 0.f, 0.f, e_per_mm * std::abs(target_x - writer.x()), fill_feed); + if (writer.y() + 0.5f * y_step > last_row_y) + break; + writer.travel(writer.x(), writer.y() + y_step, 7200); + m_left_to_right = !m_left_to_right; + } + } + Vec2f end_of_ramming(writer.x(),writer.y()); writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier @@ -1934,7 +1965,18 @@ void WipeTower2::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width); - if (do_ramming) + if (planned_ramming_depth > 0.f) { + // Orca: restart the wipe at the left-edge boundary on a fresh row below the + // quantized ram band so the entry scrub always runs at the wall gap (BBL keeps + // CP_TOOLCHANGE_WIPE starting at a box corner the same way). Same lattice + // formula as the no-ram branch below, offset by the ram band. + const float wipe_dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; + const float wipe_line_width = m_perimeter_width * m_extra_flow; + writer.travel(Vec2f(ramming_start_pos.x(), + cleaning_box.ld.y() + m_depth_traversed + planned_ramming_depth + wipe_dy - (m_perimeter_width + wipe_line_width) / 2.f + m_perimeter_width), 2400.f); + m_left_to_right = true; + } + else if (do_ramming) writer.travel(pos, 2400.f); else { // Orca: with no ram printed there is no ramming geometry to align with. Start the @@ -2370,11 +2412,18 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool // (same condition as its do_ramming), otherwise the unprinted reservation // leaves blank bands between the purge boxes. const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[old_tool].multitool_ramming; + // Orca: with the gap wall on a multi-tool printer the ram band is quantized up to + // the whole reserved rows and the wipe restarts at the left-edge boundary on a + // fresh row below it (BBL parity: the old-tool purge is whole rows and the wipe + // always starts at the box corner, where the entry scrub runs). SEMM keeps the + // stock continue-from-ram-end behavior. Must match toolchange_Unload()/tool_change(). + const bool boundary_wipe_start = do_ramming && !m_semm && m_use_gap_wall; float ramming_depth = do_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0; // first_wipe_line rides for free on the last (partially used) ramming row, which - // is already covered by ramming_depth. Without ramming that row does not exist, - // so the whole wipe volume needs reserved wiping depth. - float first_wipe_line = do_ramming ? - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width) : 0.f; + // is already covered by ramming_depth. Without ramming that row does not exist + // (and with a boundary wipe start the ram band is quantized to whole rows), so + // the whole wipe volume needs reserved wiping depth. + float first_wipe_line = (do_ramming && !boundary_wipe_start) ? - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width) : 0.f; float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height);