From b90ac13d86d5b13d64b4ba488669671e00bf18e2 Mon Sep 17 00:00:00 2001 From: denis svinarchuk Date: Thu, 16 Jul 2026 18:00:22 +0100 Subject: [PATCH] =?UTF-8?q?fix(H2C):=20carousel=20nozzle=20change=20zone?= =?UTF-8?q?=20=E2=80=94=20ramming=20depth,=20M632=20barriers,=20preheat=20?= =?UTF-8?q?suppression?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add nozzle_change_depth override for carousel rotations (same extruder, different nozzle) in plan_toolchange() and plan_tower_new() — previously depth was 0 because condition checked m_filament_map (extruder-level) instead of m_filament_nozzle_map (nozzle-level) - Emit M632/M633 carousel barriers inside NOZZLE_CHANGE_START/END markers in nozzle_change_new(), matching BBS ramming() L3421-3429 pattern - Add is_extruder_change flag to NozzleChangeResult (matches BBS field) to propagate carousel/extruder-change distinction downstream - Suppress duplicate M632 in add_M104_by_requirement for carousel — M632 already emitted inside nozzle_change_new(); extruder change preheat M104 preserved (BBS has no add_M104_by_requirement at all) Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp ramming() extruder_change flag, is_need_ramming(), plan_toolchange() --- src/libslic3r/GCode/WipeTower.cpp | 85 +++++++++++++++++++++++++++++-- src/libslic3r/GCode/WipeTower.hpp | 9 ++++ src/libslic3r/Print.cpp | 3 ++ 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 58df527da5..df173740b2 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1893,7 +1893,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int .set_initial_tool(m_current_tool) .set_extrusion_flow(m_extrusion_flow) .set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f)) - .append("; Nozzle change start\n"); + .append(format_nozzle_change_tag(true, old_filament_id, new_filament_id)); box_coordinates cleaning_box(Vec2f(m_perimeter_width, m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, (new_filament_id != (unsigned int) (-1) ? wipe_depth + m_depth_traversed - m_perimeter_width : m_wipe_tower_depth - m_perimeter_width)); @@ -1969,7 +1969,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int } } - writer.append("; Nozzle change end\n"); + writer.append(format_nozzle_change_tag(false, old_filament_id, new_filament_id)); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos(); @@ -2546,6 +2546,21 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; depth += nozzle_change_depth; } + // Vortek H2C override: carousel rotation (same extruder, different nozzle) also needs ramming depth + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp plan_toolchange() L2862 is_need_ramming() + if (nozzle_change_depth == 0 + && !m_filament_nozzle_map.empty() + && old_tool < m_filament_nozzle_map.size() && new_tool < m_filament_nozzle_map.size() + && m_filament_nozzle_map[old_tool] != m_filament_nozzle_map[new_tool]) { + double e_flow = nozzle_change_extrusion_flow(layer_height_par); + double length = m_filaments_change_length[old_tool] / e_flow; + int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width) + 1; + if (has_tpu_filament()) + nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width; + else + nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; + depth += nozzle_change_depth; + } WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume); tool_change.nozzle_change_depth = nozzle_change_depth; m_plan.back().tool_changes.push_back(tool_change); @@ -2819,6 +2834,18 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol && is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) { m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange); } + // Vortek H2C override: also trigger nozzle_change for carousel rotations within same extruder. + // Original Orca code above only checks m_filament_map (extruder-level). For H2C carousel, + // filaments on the same extruder but different nozzles also need ramming. + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp tool_change_new() L3244 + // BBS uses is_need_ramming() = !are_filaments_same_nozzle() for nozzle-level gating. + if (m_nozzle_change_result.gcode.empty() + && !m_filament_nozzle_map.empty() + && m_current_tool < m_filament_nozzle_map.size() && new_tool < m_filament_nozzle_map.size() + && m_filament_nozzle_map[m_current_tool] != m_filament_nozzle_map[new_tool] + && is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) { + m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange); + } size_t old_tool = m_current_tool; float wipe_depth = 0.f; @@ -2996,7 +3023,16 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, .set_z(m_z_pos) .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f)) - .append("; Nozzle change start\n"); + .append(format_nozzle_change_tag(true, old_filament_id, new_filament_id)); + + // Vortek H2C: carousel barrier inside nozzle change zone (emit M632/M633 ONLY for carousel = !extruder_change) + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp ramming() L3421-3429 + bool extruder_change = !is_in_same_extruder(old_filament_id, new_filament_id); + if (!extruder_change && m_is_multiple_nozzle) { + writer.append("M632 S" + std::to_string(new_filament_id) + " M N\n"); + // Precool NOT emitted here — PreCoolingInjector handles it in PostProcessor + writer.append("M633\n"); + } WipeTowerBlock* block = get_block_by_category(m_filpar[old_filament_id].category, false); if (!block) { @@ -3080,12 +3116,20 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, } } - writer.append("; Nozzle change end\n"); + // Vortek H2C: re-arm carousel barrier after ramming (BBS ramming() L3493-3497, L3535) + if (!extruder_change && m_is_multiple_nozzle) { + writer.append("M632 S" + std::to_string(new_filament_id) + " M N\n"); + writer.append("M633\n"); + } + + writer.append(format_nozzle_change_tag(false, old_filament_id, new_filament_id)); result.start_pos = writer.start_pos_rotated(); result.origin_start_pos = initial_position; result.end_pos = writer.pos_rotated(); result.gcode = writer.gcode(); + // Matches BBS: result.is_extruder_change = extruder_change (ramming() L3544) + result.is_extruder_change = extruder_change; return result; } @@ -3534,6 +3578,10 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat auto add_M104_by_requirement = [&writer, &format_line_M104, &should_heating, this]() { if (m_filpar[m_current_tool].filament_cooling_before_tower < EPSILON) return; if (!should_heating) return; + // Vortek H2C: skip for carousel — M632+M633 already emitted inside nozzle_change_new() + // For extruder change (!is_extruder_change = carousel), M632 already inside + // Reference to BBS: BBS ramming() L3421 — M632 only for !extruder_change; result.is_extruder_change L3544 + if (!m_nozzle_change_result.is_extruder_change) return; float target_temp = is_first_layer() ? m_filpar[m_current_tool].nozzle_temperature_initial_layer : m_filpar[m_current_tool].nozzle_temperature; writer.append(format_line_M104(target_temp, m_filament_map[m_current_tool] - 1)); }; @@ -3710,6 +3758,20 @@ bool WipeTower::is_in_same_extruder(int filament_id_1, int filament_id_2) return m_filament_map[filament_id_1] == m_filament_map[filament_id_2]; } +// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload. +// Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp format_nozzle_change_line() L3388 +std::string WipeTower::format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const +{ + const std::string &tag = start ? GCodeProcessor::Nozzle_Change_Start_Tag : GCodeProcessor::Nozzle_Change_End_Tag; + int old_nozzle = (old_filament_id >= 0 && old_filament_id < (int)m_filament_nozzle_map.size()) + ? m_filament_nozzle_map[old_filament_id] : -1; + int new_nozzle = (new_filament_id >= 0 && new_filament_id < (int)m_filament_nozzle_map.size()) + ? m_filament_nozzle_map[new_filament_id] : -1; + char buff[96]; + snprintf(buff, sizeof(buff), ";%s OF%d NF%d ON%d NN%d\n", tag.c_str(), old_filament_id, new_filament_id, old_nozzle, new_nozzle); + return std::string(buff); +} + // Per-extruder printable-height clamp: is an extruder still allowed to print on this wipe-tower layer, // or is it its final layer above the extruder's printable height? // Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (1-based map, layer-static), @@ -3908,6 +3970,21 @@ void WipeTower::plan_tower_new() nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; depth += nozzle_change_depth; } + // Vortek H2C override: carousel rotation also needs ramming depth + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp plan_tower_new() uses is_need_ramming() + if (nozzle_change_depth == 0 + && !m_filament_nozzle_map.empty() + && toolchange.old_tool < (int)m_filament_nozzle_map.size() && toolchange.new_tool < (int)m_filament_nozzle_map.size() + && m_filament_nozzle_map[toolchange.old_tool] != m_filament_nozzle_map[toolchange.new_tool]) { + double e_flow = nozzle_change_extrusion_flow(m_plan[idx].height); + double length = m_filaments_change_length[toolchange.old_tool] / e_flow; + int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width) + 1; + if (has_tpu_filament()) + nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width; + else + nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; + depth += nozzle_change_depth; + } toolchange.nozzle_change_depth = nozzle_change_depth; toolchange.required_depth = depth; } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 4430a3218e..c77274251e 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -58,6 +58,8 @@ public: Vec2f origin_start_pos; // not rotated std::vector wipe_path; + // Matches BBS: BambuStudio/src/libslic3r/GCode/WipeTower.hpp NozzleChangeResult::is_extruder_change + bool is_extruder_change{true}; }; struct ToolChangeResult @@ -309,6 +311,9 @@ public: int get_number_of_toolchanges() const { return m_num_tool_changes; } void set_filament_map(const std::vector &filament_map) { m_filament_map = filament_map; } + // Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.hpp set_nozzle_group_result() + void set_filament_nozzle_map(const std::vector &nozzle_map) { m_filament_nozzle_map = nozzle_map; } void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; } bool has_tpu_filament() const { return m_has_tpu_filament; } @@ -395,6 +400,9 @@ public: void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false); int get_filament_category(int filament_id); bool is_in_same_extruder(int filament_id_1, int filament_id_2); + // Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.cpp format_nozzle_change_line() + std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const; void reset_block_status(); int get_wall_filament_for_all_layer(); // for generate new wipe tower @@ -453,6 +461,7 @@ private: size_t m_cur_layer_id; NozzleChangeResult m_nozzle_change_result; std::vector m_filament_map; + std::vector m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id bool m_has_tpu_filament{false}; bool m_is_multi_extruder{false}; bool m_use_gap_wall{false}; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b20374cc30..13cad60d64 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -3940,6 +3940,9 @@ void Print::_make_wipe_tower() m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders()); wipe_tower.set_has_tpu_filament(this->has_tpu_filament()); wipe_tower.set_filament_map(this->get_filament_maps()); + // Vortek H2C: pass nozzle-level map for carousel rotation detection in tool_change_new() + // Reference to BBS: BambuStudio/src/libslic3r/GCode/WipeTower.hpp set_nozzle_group_result() + wipe_tower.set_filament_nozzle_map(this->get_filament_nozzle_maps()); // Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from // the full config — no shipping profile sets it) and the shared printable bed used by the PETG // pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.