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.
Six issues found by reviewing the previous commit against belt-printer, two of
them release-blocking.
Data race (high). Print::process() runs generate_support_material() for all
objects in a tbb::parallel_for, and make_belt_brim() runs at its tail, but
belt_brim_obstacles() read every OTHER object's support_layers() - which a
concurrent task may be inside clear_support_layers() deleting. That is a
use-after-free, and even when it survives, the obstacle set depends on which
object finishes first. Only this object's own supports are consulted now; they
are complete at that point. Foreign objects still contribute their slices,
which are finished and immutable before the support phase.
Apron bands dropped (high), two separate causes. An apron band prints below
its own object's first layer, but another object can already be printing at
that print_z, in which case process_layer() takes the ordinary path and never
emitted the band - the emission is now shared by both paths. Separately, a
band whose print_z matched a support layer of the SAME object was overwritten
in the print-wide merge, which keeps one record per object per z and could not
detect the collision because LayerToPrint::layer() is null for a band. The
per-object pairing loop is now a three-way merge over object, support and apron
streams, so each object contributes at most one record per z.
Multi-instance was far too strict (medium). It refused belt brim for every
multi-instance object, killing plain brim width and inner brim too, and only
warned when a leading length was set. Only movement ALONG the belt changes an
instance's belt-floor Z, so copies side by side ACROSS the belt share one set of
bands perfectly well; belt_brim_instances_compatible() now tests just that, and
the warning fires whenever the brim is actually suppressed.
Apron layer bookkeeping (medium). Apron layers count toward m_layer_count and
advance m_layer_index, but emitted no Z/height tags, left m_last_layer_z,
m_max_layer_z and m_last_height stale - so the first object layer computed its
height against a pre-apron Z - and skipped before_layer_change_gcode and
layer_change_gcode entirely. All of that now matches the ordinary path.
Obstacle cost (low). belt_brim_obstacles() ran a full-plate union per band.
A bounding-box pre-filter drops non-overlapping objects before materialising any
polygon, and the union is skipped for trivial inputs.
Deliberately unchanged: every apron band still reports cooling layer_id 0.
CoolingBuffer uses it for the initial_layer_fan_speed override and the
close_fan_the_first_x_layers gate, and every band lies on the belt plane itself,
so it is all first-layer material by the only definition that means anything on
a belt. Numbering the bands would ramp the fan up while still printing on the
belt. Now documented at the assignment rather than left implicit.
A belt printer slices in a rotated frame, so the belt surface is a tilted
plane rather than the Z=0 bed plane. Each slicing layer touches the belt
only along a narrow strip at its leading edge - about 0.2mm at 45 degrees -
so a part's first layer is really a first line, with almost no contact patch
to hold it down while the belt drags it forward. Brim was hard-disabled on
belt printers, leaving no remedy at all.
Generate the brim on the belt plane instead. The object's belt footprint is
the union over layers of each slice clipped to that layer's contact band; the
brim is offset from it in a "flattened" frame where the shear axis is
stretched by 1/cos(tilt), so ordinary Clipper offsets measure true on-belt
distance. It is emitted as cross-belt lines, one per layer band, anchored to
a fixed fraction of the band so every line shares a nozzle-to-belt clearance
and therefore comes out the same width; flow is matched to the resulting band
pitch, keeping the sheet uniform and gap-free.
Three new controls, all belt-only:
* Leading brim length - extends the brim ahead of the part along the belt,
on every downhill-facing edge of its contact area. This apron necessarily
prints BELOW the object's first layer, since layer 0 is the part's leading
contact, so it needs brim-only bands of its own.
* Extra brim width - widens the brim sideways across the belt only.
* Brim type "Leading edge only" - brim at the part's first belt contact and
nothing after it. Appended last in BrimType so no existing value shifts;
degrades to an outer brim off belt printers, with a warning.
The apron bands are lightweight records rather than a Layer subclass, so no
fabricated Layer::id() can leak into initial-layer temperature selection, the
spiral vase probe, cooling or gradual interpolation. They are generated in
posSupportMaterial because their print_z values must exist before ToolOrdering
is built at psWipeTower, and they are emitted from a short dedicated branch in
process_layer that runs before any layer pointer is dereferenced.
The footprint is closed before offsetting outwards: a belt contact patch is
often a broken-up strip, and the merged offset rings of two islands closer
than 2 x brim_width would otherwise fill the space between them - space that
lies under the part.
Also fixes a pre-existing bug where PrintObject::get_first_layer_bbox()
overwrote a valid bbox with an unassigned one on any belt printer with a brim
configured, because has_brim() was true while make_brim() returned early.
Belt brim is refused alongside the prime tower and spiral vase, and requires
one instance per PrintObject - translating an instance along the belt axis
changes its physical belt-floor Z. Untilted belt printers are unchanged: they
still get no brim, since the plate brim is emitted out of skirt_brim_groups(),
which _make_skirt() never builds for a belt printer.