From 6cf747808c79c913665e8c03a7d811329d3d8290 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Wed, 30 Sep 2026 15:44:18 -0500 Subject: [PATCH] Belt: never start a scarf joint seam below the layer A scarf joint begins one layer height below the current layer and ramps up along the wall. On a tilted belt that start is a step backwards along the belt axis, into the previous layer's wall at the seam: 0.283 mm per 0.2 mm layer at 45 degrees. With an aligned seam the nozzle rams the same spot on every layer. A BabyBelt Pro benchy with seam_slope_type=external showed 601 such back-steps from layer 107 on, and in the field the belt "jumped backwards" and the head knocked the part loose. Belt printers now skip the scarf in GCode::extrude_loop, and the process tab greys the scarf controls out for them, as it already does for arc fitting. The regression test slices a cube on a belt with the scarf enabled and checks the belt axis never steps back by a layer pitch. --- src/libslic3r/GCode.cpp | 7 +++- src/slic3r/GUI/ConfigManipulation.cpp | 6 ++-- tests/fff_print/test_print.cpp | 51 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ed2dd400d4..fae8860b7b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7661,8 +7661,13 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref, loop.split_at(last_pos, false); const auto seam_scarf_type = m_config.seam_slope_type.value; + // Belt printers never get a scarf joint. The scarf starts one layer height + // below the layer, which on a tilted belt is a step backwards along the belt + // axis into the previous layer's wall at the seam (0.28 mm at 45 degrees per + // 0.2 mm layer); with an aligned seam that ram repeats at the same spot on + // every layer and knocks the part loose. bool enable_seam_slope = ((seam_scarf_type == SeamScarfType::External && !is_hole) || seam_scarf_type == SeamScarfType::All) && - !m_config.spiral_mode && + !m_config.spiral_mode && !m_config.belt_printer.value && (loop.role() == erExternalPerimeter || (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && layer_id() > 0; const auto nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 65c2df092c..ac3ffa9006 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -1193,8 +1193,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in toggle_line("small_area_infill_flow_compensation_model", have_small_area_infill_flow_compensation); - toggle_field("seam_slope_type", !has_spiral_vase); - bool has_seam_slope = !has_spiral_vase && config->opt_enum("seam_slope_type") != SeamScarfType::None; + // Belt printers: the scarf would start one layer back along the belt, inside the + // previous layer (GCode::extrude_loop skips it there too). + toggle_field("seam_slope_type", !has_spiral_vase && !is_belt_printer); + bool has_seam_slope = !has_spiral_vase && !is_belt_printer && config->opt_enum("seam_slope_type") != SeamScarfType::None; toggle_line("seam_slope_conditional", has_seam_slope); toggle_line("seam_slope_start_height", has_seam_slope); toggle_line("seam_slope_entire_loop", has_seam_slope); diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 78d8e0fd72..511a627fc1 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -20,6 +20,7 @@ #include "test_utils.hpp" #include +#include #include #include @@ -508,3 +509,53 @@ TEST_CASE("Sequential printing publishes the nozzle group result", "[Print][Mult CHECK(gcode.find("; SEQ-ND-OK") != std::string::npos); } } + +// A scarf joint starts one layer height below the layer and ramps up along the +// wall. On a tilted belt that start is a step backwards along the belt axis, into +// the previous layer's wall at the seam: 0.283 mm per 0.2 mm layer at 45 degrees. +// With an aligned seam the nozzle rams the same spot on every layer (field report +// from a BabyBelt Pro: the belt "jumped backwards" and knocked the part loose). +// Belt printers therefore never get a scarf, whatever the process preset says. +TEST_CASE("Belt printers never start a scarf seam below the layer", "[Print][belt][Seam]") +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "belt_printer", 1 }, + { "belt_slice_rotation", "x" }, + { "belt_slice_rotation_angle", 45 }, + { "belt_slice_rotation_global", 1 }, + { "gcode_remap_x", "rev_x" }, + { "gcode_remap_y", "pos_z" }, + { "gcode_remap_z", "pos_y" }, + { "layer_height", 0.2 }, + { "initial_layer_print_height", 0.2 }, + { "skirt_loops", 0 }, + { "top_shell_layers", 0 }, + { "bottom_shell_layers", 1 }, + { "wall_loops", 2 }, + { "seam_position", "back" }, + { "seam_slope_type", "external" }, + { "seam_slope_inner_walls", 1 }, + { "seam_slope_start_height", 0 }, + { "machine_start_gcode", "T[initial_tool]\n" }, + { "layer_change_gcode", "G92 E0\n" }, + }); + const std::string gcode = slice({ cube(20) }, config); + REQUIRE(! gcode.empty()); + + // The belt axis is machine Z. Within a layer it only drifts by the frame + // coupling (well under 0.1 mm across a 20 mm cube); a scarf start is a full + // layer pitch (0.283 mm) backwards. + double last_z = std::numeric_limits::lowest(); + double worst_backstep = 0.; + GCodeReader parser; + parser.parse_buffer(gcode, [&](GCodeReader &, const GCodeReader::GCodeLine &line) { + if (! line.cmd_is("G1") || ! line.has_z()) + return; + const double z = line.z(); + if (last_z != std::numeric_limits::lowest()) + worst_backstep = std::max(worst_backstep, last_z - z); + last_z = z; + }); + CHECK(worst_backstep < 0.2); +}