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 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-10-07 19:08:59 -05:00
co-authored by Claude Fable 5.1
parent cfad587c2d
commit ed8c1f92d7
2 changed files with 48 additions and 6 deletions
+13 -5
View File
@@ -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;
+35 -1
View File
@@ -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<double>::max());
REQUIRE(peri_z < std::numeric_limits<double>::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