diff --git a/src/libslic3r/BeltBrim.cpp b/src/libslic3r/BeltBrim.cpp index 920790891c..d51f1248de 100644 --- a/src/libslic3r/BeltBrim.cpp +++ b/src/libslic3r/BeltBrim.cpp @@ -443,6 +443,8 @@ void make_belt_brim(PrintObject &object) // own contact band. This is the object's bottom face, which on a belt is // spread over every layer instead of sitting in layer 0. ExPolygons footprint_acc; + // The first layer that touches the belt: where the leading-edge brim is cut. + const Layer *first_contact = nullptr; for (size_t i = 0; i < nlayers; ++ i) { const Layer &layer = *object.layers()[i]; if (layer.lslices.empty()) @@ -458,7 +460,12 @@ void make_belt_brim(PrintObject &object) const Polygon band = band_box(bb, bc.frame.from_axis, u_lo, u_hi); if (band.empty()) continue; - expolygons_append(footprint_acc, intersection_ex(layer.lslices, Polygons{ band })); + ExPolygons contact = intersection_ex(layer.lslices, Polygons{ band }); + if (contact.empty()) + continue; + if (first_contact == nullptr) + first_contact = &layer; + expolygons_append(footprint_acc, std::move(contact)); } const ExPolygons footprint = union_ex(footprint_acc); if (footprint.empty()) @@ -488,19 +495,13 @@ void make_belt_brim(PrintObject &object) width, gap, leading, lateral, bc.frame), bc.frame); - if (bt == btLeadingEdgeOnly && ! bc.region.empty()) { + if (bt == btLeadingEdgeOnly && first_contact != nullptr && ! 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; + // belongs to later contacts. The first contact is the first layer that touches + // the belt (step 1), neither layers().front(), an empty lead-in layer, nor the + // first layer with geometry, which is an overhang's tip when the part overhangs + // its leading end: both lie ahead of the part. 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 639c36d6c7..c51dd5ebc6 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -1158,9 +1158,10 @@ 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. +// with the belt. The cut has to be taken at the first layer that touches the belt: 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) { @@ -1191,6 +1192,44 @@ TEST_CASE("Leading-edge-only brim is laid at the first contact and nowhere else" CHECK(leading_layers < outer_layers); } +// An overhang on the leading side is sliced before the part reaches the belt, so the +// first layer with geometry is the overhang's tip, above the belt. A leading-edge cut +// taken there lies ahead of the part: the brim shrank to a sliver well ahead of it, or +// vanished once the overhang reached further forward than the brim. The overhang does +// not touch the belt, so it must not change the brim at all. +TEST_CASE("Leading-edge-only brim ignores an overhang ahead of the part", "[SkirtBrim][belt][Regression]") +{ + const double fin_length = GENERATE(30., 40.); + CAPTURE(fin_length); + // A 20 mm cube, with or without a 2 mm thick fin leaving its top edge and reaching + // `fin` toward -Y, the end of the part that prints first. The fin overlaps the cube + // by 1 mm so the two shells merge instead of sharing a face. + auto brim_layers = [](double fin) { + indexed_triangle_set its = its_make_cube(20., 20., 20.); + if (fin > 0.) { + indexed_triangle_set fin_its = its_make_cube(20., fin + 1., 2.); + its_translate(fin_its, Vec3f(0.f, float(-fin), 18.f)); + its_merge(its, fin_its); + } + DynamicPrintConfig config = belt_brim_config(); + config.set_deserialize_strict({ + { "brim_type", "leading_edge_only" }, + { "brim_width", 5 }, + { "leading_brim_length", 10 }, + { "extra_brim_width", 0 }, + { "brim_object_gap", 0 }, + }); + return role_layers(slice({ TriangleMesh(std::move(its)) }, config), "brim"); + }; + const int plain = brim_layers(0.); + const int with_fin = brim_layers(fin_length); + INFO("leading-edge brim layers: plain cube " << plain << ", with the fin " << with_fin); + REQUIRE(plain > 0); + // One layer of slack: the fin widens the part's footprint on the plate, which can + // move the layer grid by a fraction of a layer. + CHECK(std::abs(with_fin - plain) <= 1); +} + 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