Belt brim: lattice lines closer to the belt than the band fraction move uphill

With a first layer of about 0.28 mm or more at 45 degrees (or a shallower
belt) the brim band is wider than one bead and its lines go on the nominal
lattice. A lattice line could land where the belt is almost at the band's
print_z; its flow was clamped to half a layer while the nozzle sat nearly on
the belt. Such a line now moves uphill to the 0.75 fraction the single-line
case uses, and a line that lands on the previous one is skipped.

Ported from the Unlayered fork (patch 0007 of its belt port series, found
there by fuzzing first layer heights). The fork's companion fix, restricting
the brim filament to those the writer was handed (0008), is not needed here:
ToolOrdering registers the brim filament on every band's layer, so the writer
always has it. A test pins that with every object a flush target.
This commit is contained in:
harrierpigeon
2026-10-02 01:13:29 -05:00
parent 412564cae3
commit 3beb448ae6
2 changed files with 86 additions and 12 deletions
+28 -12
View File
@@ -1,3 +1,4 @@
#include <limits>
#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<coord_t>::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.
+58
View File
@@ -15,6 +15,7 @@
#include <limits>
#include <map>
#include <set>
#include <sstream>
#include <string>
#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<double> 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<std::vector<Slic3r::ConfigBase::SetDeserializeItem>> 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);
}