diff --git a/src/libslic3r/BeltBrim.cpp b/src/libslic3r/BeltBrim.cpp index fa119c6609..92877ef0aa 100644 --- a/src/libslic3r/BeltBrim.cpp +++ b/src/libslic3r/BeltBrim.cpp @@ -1,3 +1,4 @@ +#include #include "BeltBrim.hpp" #include "ClipperUtils.hpp" @@ -284,7 +285,33 @@ static void belt_brim_band_paths(const BeltBrimContext &bc, // must not be pooled before the flow is resolved. // Overshoot the region so the clip, not the line's ends, decides the extent. const coord_t margin = coord_t(SCALED_EPSILON) + 1; - for (const coord_t u : us) { + coord_t u_prev = std::numeric_limits::min(); + for (coord_t u : us) { + // Nozzle-to-belt clearance for this line. Constant along the line, because the + // belt height depends only on the shear-axis coordinate. Band-anchored lines + // share one clearance by construction; lattice lines (shallow belts, or a first + // layer thick enough that the band is wider than a bead) each get their own. + // + // A lattice line can fall where the belt is only a hair below the band's print_z. + // The bead there would be laid scraping the belt while its flow is sized for a + // taller cell, so it is moved uphill to the same fraction of the band the + // single-line case uses. (The clearance is along slice Z; the real gap under the + // nozzle is clearance x cos(tilt), 0.53 h at 45 degrees for the 0.75 fraction.) + double clearance = uniform_clearance; + if (clearance <= 0.) { + const Point probe = bc.frame.from_axis == 0 ? Point(u, 0) : Point(0, u); + clearance = print_z - bc.ctx.floor_print_z(probe); + if (clearance < BAND_CLEARANCE_FRACTION * height) { + clearance = BAND_CLEARANCE_FRACTION * height; + u = scale_(bc.ctx.cutoff_u(print_z - clearance)); + } + clearance = std::min(clearance, height); + } + // Two lattice lines moved to the same place are one line. + if (u == u_prev) + continue; + u_prev = u; + Polyline line; if (bc.frame.from_axis == 0) line.points = { Point(u, coord_t(bc.region_bbox.min.y() - margin)), @@ -299,17 +326,6 @@ static void belt_brim_band_paths(const BeltBrimContext &bc, if (pieces.empty()) continue; - // Nozzle-to-belt clearance for this line. Constant along the line, because the - // belt height depends only on the shear-axis coordinate. Band-anchored lines - // share one clearance by construction; lattice lines (shallow belts) each get - // their own, clamped so neither end of a band yields an unprintable bead. - double clearance = uniform_clearance; - if (clearance <= 0.) { - const Point probe = bc.frame.from_axis == 0 ? Point(u, 0) : Point(0, u); - clearance = print_z - bc.ctx.floor_print_z(probe); - clearance = std::min(std::max(clearance, 0.5 * height), height); - } - // with_cross_section, not with_height: it reaches the prescribed volume while // KEEPING the extrusion spacing, so the bead is sized to fill exactly one // pitch x clearance cell of the sheet. diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 5fa810106f..edd2f0a56a 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "test_helpers.hpp" // get access to init_print, etc @@ -1195,3 +1196,60 @@ TEST_CASE("Belt brim coexists with support material", "[SkirtBrim][belt]") REQUIRE(! gc.empty()); CHECK(role_passes(gc, "brim") > 0); } + +// With a 0.3 mm first layer at 45 degrees the brim band on the belt is wider than one bead, +// so its lines go on the nominal lattice instead of at a fixed fraction of the band. A +// lattice line can then land where the belt is almost at the band's print_z; it must be +// moved uphill to the same 0.75 fraction the single-line case uses, not laid scraping the +// belt with its flow clamped to half a layer. +TEST_CASE("Belt brim lattice lines keep their clearance above the belt", "[SkirtBrim][belt]") +{ + DynamicPrintConfig config = belt_brim_config(); + config.set_deserialize_strict({ + { "layer_height", 0.3 }, + { "initial_layer_print_height", 0.3 }, + { "brim_type", "outer_only" }, + { "brim_width", 4 }, + { "brim_object_gap", 0 }, + }); + const std::string gcode = slice({ cube(20) }, config); + + // Heights of the brim extrusions, from the ;HEIGHT: tags inside ;TYPE:Brim sections. + std::vector brim_heights; + bool in_brim = false; + std::istringstream lines(gcode); + for (std::string line; std::getline(lines, line); ) { + if (boost::starts_with(line, ";TYPE:")) + in_brim = boost::starts_with(line, ";TYPE:Brim"); + else if (in_brim && boost::starts_with(line, ";HEIGHT:")) + brim_heights.push_back(std::stod(line.substr(8))); + } + REQUIRE(! brim_heights.empty()); + for (const double h : brim_heights) { + CHECK(h >= 0.75 * 0.3 - 1e-3); + CHECK(h <= 0.3 + 1e-3); + } +} + +// The brim prints in the object's outer wall filament even when every extrusion of the object +// is offered to purging (flush_into_objects): the tool ordering registers the brim filament +// itself, so the writer always knows it. +TEST_CASE("Belt brim slices when every object is a flush target", "[SkirtBrim][belt]") +{ + DynamicPrintConfig config = belt_brim_multifilament_config(2, { + { "brim_type", "outer_only" }, + { "brim_width", 4 }, + { "brim_object_gap", 0 }, + { "flush_into_objects", 1 }, + { "flush_into_infill", 1 }, + }); + const std::vector> overrides { + { { "extruder", 1 } }, { { "extruder", 2 } }, + }; + Print print; + Model model; + init_print({ cube(20), cube(20) }, print, model, config, &overrides); + REQUIRE(print.validate().string.empty()); + const std::string out = gcode(print); + CHECK(out.find(";TYPE:Brim") != std::string::npos); +}