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