From ed8c1f92d7c43b4f38c598b59a5da273660d48bd Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Wed, 7 Oct 2026 19:08:58 -0500 Subject: [PATCH] Belt brim: cut the leading-edge brim at the first contact layer The leading-edge-only brim is the outer brim cut down to the part's first contact with the belt, and the cut was taken at layers().front(). Since the slicing frame starts at the belt below the footprint (#16236) that is an empty lead-in layer whose contact lies ahead of the part, so the cut removed the whole region and the brim type produced no brim at all. Take the cut at the first layer with geometry. The all-brim-types test now includes leading_edge_only, and a new test checks that the brim starts no later than the part and covers fewer layers than the outer brim. Co-Authored-By: Claude Fable 5.1 --- src/libslic3r/BeltBrim.cpp | 18 +++++++++++---- tests/fff_print/test_skirt_brim.cpp | 36 ++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/BeltBrim.cpp b/src/libslic3r/BeltBrim.cpp index a25659b30e..920790891c 100644 --- a/src/libslic3r/BeltBrim.cpp +++ b/src/libslic3r/BeltBrim.cpp @@ -488,11 +488,19 @@ void make_belt_brim(PrintObject &object) width, gap, leading, lateral, bc.frame), bc.frame); - if (bt == btLeadingEdgeOnly && ! bc.region.empty()) - // The cut is the uphill edge of the first layer's contact band: everything - // past it belongs to later contacts. - bc.region = belt_brim_clip_leading_edge(bc.region, bc.frame, - bc.ctx.cutoff_u(object.layers().front()->print_z)); + if (bt == btLeadingEdgeOnly && ! bc.region.empty()) { + // The cut is the uphill edge of the first contact's band: everything past it + // belongs to later contacts. The first contact is the first layer with + // geometry, not layers().front(): the slicing frame starts at the belt below + // the footprint, so the leading layers are empty and their contact lies ahead + // of the part. + const Layer *first_contact = nullptr; + for (const Layer *layer : object.layers()) + if (! layer->lslices.empty()) { first_contact = layer; break; } + if (first_contact == nullptr) + return; + bc.region = belt_brim_clip_leading_edge(bc.region, bc.frame, bc.ctx.cutoff_u(first_contact->print_z)); + } if (bc.region.empty()) return; diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 34f71a71a7..639c36d6c7 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -1140,7 +1140,7 @@ TEST_CASE("Every brim type slices on a belt printer", "[SkirtBrim][belt]") // Auto / Mouse ear / Painted collapse to outer-only rather than crashing or // silently producing nothing. const char *brim_type = GENERATE("auto_brim", "brim_ears", "painted", "outer_only", - "inner_only", "outer_and_inner", "no_brim"); + "inner_only", "outer_and_inner", "leading_edge_only", "no_brim"); DYNAMIC_SECTION("brim_type " << brim_type) { DynamicPrintConfig config = belt_brim_config(); config.set_deserialize_strict({ @@ -1157,6 +1157,40 @@ TEST_CASE("Every brim type slices on a belt printer", "[SkirtBrim][belt]") } } +// The leading-edge-only brim is the outer brim cut down to the part's first contact +// with the belt. The cut has to be taken at the first layer with geometry: the slicing +// frame starts at the belt below the footprint, so layers().front() is an empty lead-in +// layer whose contact lies ahead of the part, and a cut taken there left no brim at all. +TEST_CASE("Leading-edge-only brim is laid at the first contact and nowhere else", "[SkirtBrim][belt][Regression]") +{ + auto brim_gcode = [](const char *brim_type) { + DynamicPrintConfig config = belt_brim_config(); + config.set_deserialize_strict({ + { "brim_type", brim_type }, + { "brim_width", 5 }, + { "leading_brim_length", 10 }, + { "extra_brim_width", 0 }, + { "brim_object_gap", 0 }, + }); + return slice({ cube(20) }, config); + }; + const std::string leading = brim_gcode("leading_edge_only"); + const std::string outer = brim_gcode("outer_only"); + + const double brim_z = first_role_z(leading, "brim"); + const double peri_z = first_role_z(leading, "perimeter"); + REQUIRE(brim_z < std::numeric_limits::max()); + REQUIRE(peri_z < std::numeric_limits::max()); + // At the first contact: the brim starts no later than the part does... + CHECK(brim_z <= peri_z + EPSILON); + // ...and stops there, while the outer brim keeps following the footprint. + const int leading_layers = role_layers(leading, "brim"); + const int outer_layers = role_layers(outer, "brim"); + INFO("brim layers: leading-edge " << leading_layers << ", outer " << outer_layers); + CHECK(leading_layers > 0); + CHECK(leading_layers < outer_layers); +} + TEST_CASE("An untilted belt printer gets no brim", "[SkirtBrim][belt]") { // Belt brim needs a tilt to have a belt plane to lie on, and the flat plate brim