Removed, with the keys added to handle_legacy()'s ignore list so saved
profiles and 3MFs keep loading:
- belt_slice_rotation_global and preslice_remap_global. Both were only
consulted when belt_preslice_global ("Global mesh transforms") was off,
which no profile does; belt_preslice_global is now the single global
mode and is presumed on everywhere the old flags were ORed in
(PrintObjectSlice, BeltBackTransform, BeltGCode, Print::process,
PrintApply). The Belt tilt row is axis + angle only.
- preslice_remap_x/y/z. No profile used the pre-slice axis remap; the belt
tilt axis plus the G-code axis remap cover the machines that exist, and
its implementation only agreed with itself for a plain swap (matrix
columns vs remap_bbox rows). BeltTransformPipeline::build_preslice_remap,
remap_bbox and has_preslice_remap are gone, the forward transform is the
rotation, and the G-code header no longer carries the remap.
- belt_support_z_offset_mode. Saved and invalidated steps, but no support
generator read it.
- first_layer_plane and first_layer_plane_offset, with FirstLayerPlane.cpp.
On every shipped configuration the band is measured from the belt
surface (GCode::belt_height_above_floor) and the evaluator was only
reached for an explicit XY/YZ/XZ choice or a non-zero offset, which
nobody set. first_layer_plane_thickness stays as the band unit,
relabelled "First layer band thickness".
UI: the Machine frame transforms group is five single-option rows (G-code
remap X / Y / Z, Decouple machine-frame tilt, Machine-frame tilt angle;
the angle row is shown only when decoupled) instead of two multi-column
lines, and the remap fields carry full labels.
Also carries the phong.fs struct fix from #16226 so the worktree build
links its shaders.
libslic3r_tests and fff_print_tests pass; clang-tidy diff check clean;
orca_profile_tool.py check clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings belt-printer up to main 4b4a261787. Resolutions:
- G-code header (#15897, #15915): main moved the header, config and
thumbnail block later in _do_export; write_belt_header() moves with it,
still after the thumbnails and outside the BTT_TFT gate.
- _extrude: first-layer acceleration keeps the per-path first-layer plane
test with main's cached nozzle index (#16028); main's set_speed out-param
form (#16108) everywhere else.
- GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings
that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy,
which is the overload GCode calls, and appends to the caller's string.
- GCodeProcessorResult: the belt fields join main's forwarding assign.
- Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree
support join types lose their ClipperLib qualifier.
- CLI arrange (#15837): belt printers still reserve no wipe tower.
- Wipe tower options (#15841): the two new sparse-layer toggles are hidden
for belt printers like the rest of the tower options.
- Keyboard shortcuts (#15706): main's registry replaces the old key switch;
the belt view toggle is re-registered in the next commit.
- Print::process: the belt purge-plan undo runs before main's SliceStarted
event.
- scripts/filament_id_snapshot.json: deleted on main (a77209af8f).
- Includes and appended tests: union of both sides.
* Fix CLI Crashes on Malformed Project, Assemble List and No-Input Runs
Four CLI paths indexed vectors without checking their size and crashed
with SIGSEGV on malformed input:
- A project inherits_group whose length is not the filament count plus
the process and printer entries was split by position. It is now
ignored with a warning, as if the project had none.
- An assemble list object with an empty filaments list passed validation
and was then read at index 0. It is now rejected as a config error, as
is a negative filament id.
- --slice N --arrange 1 on a project without plate metadata read the
missing plate data. It now falls back to the plate's own filaments,
like the other plate data reads.
- --assemble with no input model built an object with no volumes. It is
now rejected as invalid parameters.
A tests/cli script covers each case through the binary, since all four
live inline in CLI::run().
* Move the Assemble List Parser into libslic3r
Behaviour-preserving move of the --load-assemble-list JSON parser and
its plate/object structs from the CLI into libslic3r/Format/AssembleList,
so the format can be unit tested. The parser returns its own
AssembleListResult and takes the plate limit as a parameter; CLI::run
maps the result to the same exit codes as before. Every validation rule
and log message is unchanged.
Adds Catch2 coverage of the valid layout and each validation rule.
* Keep the Process and Printer of an inherits_group of the Wrong Length
A project whose inherits_group did not have one entry per filament plus
the process and printer entries was loaded as if it had none. The CLI
then looked for system presets under the names of the user presets,
found none and refused to slice a project that slices on main.
The group is now read as before: the process first, the printer last
and the filaments in between, up to the filament count. A filament
without an entry counts as a system preset. A group with fewer than two
entries is still ignored. The warning stays.
* Lock Config and Preset Files Across Instances and Write Them Atomically
Every running instance shares one OrcaSlicer.conf and one user preset
tree, and nothing kept their writers apart. Two instances saving at the
same moment, or the cloud preset sync thread writing while the GUI thread
saved, could interleave, and a reader in another instance could open a
preset JSON or .info file between truncate and close and get a partial
file, dropping that preset for the session with a parse error.
Add InstanceLock, a scoped guard that serialises the threads of one
process through a recursive mutex and other processes through an advisory
OS file lock: flock on POSIX, held on the guard's own descriptor so no
other close in the process can drop it, and LockFileEx on Windows. The
outermost guard opens the lock file and closes it on release, so nothing
stays open between saves and a data dir can be removed once nothing is
saving into it; the file itself is kept, since deleting it would let a
third instance lock a fresh file while the second still holds the old
one. It is best effort: when the lock file cannot be opened or locked, or
another instance still holds it after a second, the guard logs once and
lets the write proceed, then leaves the file alone for ten seconds, so a
hung instance never blocks every other one and a holder stuck in a
debugger does not cost a stall per save. The guard sits at the leaf
readers and writers: set_sync_info_and_save() calls save_info() under the
preset collection mutex, so a batch lock around save_user_presets() would
invert the order against the sync thread. The user preset scan reads its
files on worker threads without the guard, since the mutex would
serialise them, and takes it per file in the serial commit step, so a
save never waits for the whole scan. Each read keeps the bytes of the
preset and its .info as they were before parsing; commit compares them
with the disk under the guard and reads a file that changed again, so it
never deletes or writes back over another instance's newer save, nor
installs a .json and .info from two different saves; a preset another
instance removed in the meantime is not installed. Without the guard, in
a cool-down, the scan still loads the presets but leaves their files
alone: an unreadable file stays for the next scan, and a derived
compatible printer is not written back. Read-only scans, which is what
the CLI does, take no lock and create no lock file.
AppConfig holds OrcaSlicer.conf.lock in load() and save(); load is
included because the Windows path restores from the .bak copy. Every
user preset writer and reader holds user.lock: Preset::save(), which
writes no .info when the preset itself could not be written, since an
.info without its preset reads as a cloud deletion request, save_info(),
reload() and remove_files(), each preset the scan commits, the
bundle metadata reads and write, the .info removal after a cloud-confirmed
delete, the orphaned-.info scan on the sync thread, the bundle folder
removal on unsubscribe and the physical printer writers and delete
paths. A bundle import extracts under cache/ into a folder per process
and per import, where no scan reads.
Preset JSON, .info, bundle metadata, physical printer and config files,
and the caches and state files that already used a temporary by hand,
now go through write_file_atomically(), which writes <file>.<pid>.<n>.tmp
beside the target and renames it over, so a reader that never waits sees
a complete old or new file. A symlink is followed; a target that is not
a regular file is written in place; and when no temporary can be created
beside an existing target, or the rename itself is refused, by a Windows
reader holding the file open or a mount that cannot replace in one step,
the helper writes in place as before, since losing the save is worse
than a torn read. On POSIX the rename replaces the
target atomically where the old code removed it first and left a window
with no file at all; only a mount that refuses a one-step replace gets
the old remove-then-rename. A crash between temporary and rename leaves
the temporary behind, which no scan reads. Preset::save() returns
whether it wrote the preset, so the scan counts a compatible printer it
could not write back as an error. A failed config write keeps
the config dirty, and the idle handler waits ten seconds before retrying
while an explicit save always tries.
* Run the Cross-Process Lock Test on Every Platform
The test that checks the guard yields to a lock held elsewhere forked a
child to hold it, so it was left out on Windows. The OS lock belongs to
the handle on Windows and to the open file description elsewhere, so a
second handle in the same process is refused like another instance
would be. The test now holds the lock that way and runs everywhere.
Brings in upstream/belt-printer (the Sept 14 main merge) plus Hanif Koh's
21 review-fix commits from PR #15685, on top of the MachineKinematics
refactor and the purge-prism / tree-support / first-layer-speed fixes.
Conflict resolution:
- BeltGCodeWriter is gone (kinematics refactor), so Hanif's plate-offset
fix for it is ported into GCodeWriter: the first-layer-plane checks in
travel_to_xy / travel_to_xyz / _travel_to_z now evaluate the plate-local
point, and BeltGCode::init_belt_writer hands the stored plate origin to
the writer it installs.
- init_belt_writer(Print&) takes Hanif's signature; the BBL flag is set on
the surviving writer by GCode::_do_export.
- The shared emit_belt_brim_bands() loop keeps the BeltFloorObjectGuard the
local branch added, so apron bands classify first-layer height against
their own object.
- eager_lift keeps effective_type: it now carries set_force_normal_lift().
- GCodeWriter's initializer list follows Hanif's member order with
m_kinematics in its declared position.
- TreeSupport::detect_overhangs uses Hanif's clamped build_plate_tilt_slope()
for the non-belt path and the belt shear for the belt path.
* CLI: --ground-face-* / --lay-flat / --center-on-bed orientation primitives
Adds the CLI counterparts to the GUI's lay-flat / face-pick gizmos.
Scripted / CI / AI pipelines can now set orientation without rendering
a wxWidgets frame; today the only way is a GUI round-trip.
New CLI actions (all operate in the mesh-local frame so they compose
with prior --rotate-* / --orient flags):
--ground-largest-face 1 Auto-detect the largest planar-face
or --lay-flat 1 cluster (area-weighted), rotate so its
normal points -Z. Covers "this part has
one obvious flat side" cases.
--ground-face-normal NX,NY,NZ Pick the face whose mesh-local
normal best matches the given
vector; ground it. e.g.
`--ground-face-normal 1,0,0`
stands a part on its +X side.
--ground-face-point X,Y,Z Find the triangle containing the
given mesh-local point; ground its
face. Disambiguates when several
faces share a normal (largest
containing triangle wins).
--center-on-bed 1 Translate so the XY bounding-box
centroid lands at the bed center
(derived from printable_area).
New file `src/slic3r/Utils/MeshOrient.{hpp,cpp}`:
- collect_triangles_object / compute_face_clusters — quantize
per-triangle normals (0.001, ~0.06°) and area-weighted-average
within clusters. Same clustering logic used by lay-flat.
- apply_ground_rotation — same math as Selection::flattening_rotate
in the GUI (Selection.cpp:1432): world-space quaternion from the
transformed normal to -Z, applied as offset * new_rot * old_no_offset
on every instance of every object, then a per-instance Z-lift so the
grounded face lands at exactly 0 (avoids "No layers were detected"
from FP-error z≈-1e-9).
- ground_face_point uses a top-N cluster search + point-in-triangle
test in local space; largest-area triangle wins on ambiguity.
Rationale: without these, any CLI pipeline that needs a specific
face on the bed must either encode custom rotation math per part or
break out of the pipeline into the GUI. Both are bad for
reproducibility. The --ground-face-* triple + the largest-face
auto-mode cover essentially every orientation intent expressible
in a slicing wizard.
Scope:
- `src/slic3r/Utils/MeshOrient.{hpp,cpp}` — new, ~420 lines
- `src/slic3r/CMakeLists.txt` — 2-line registration
- `src/libslic3r/PrintConfig.cpp` — 5 new CLIMiscConfigDef entries
- `src/OrcaSlicer.cpp` — 58-line handler block + 1 include
No behaviour change when the flags are absent.
(cherry picked from commit c45a9795e1)
* CLI grounding: choose among the Lay on Face planes, per object
Addresses review:
- Move the geometry of GLGizmoFlatten::update_planes() into
libslic3r/LayOnFace and use it from the gizmo and the CLI, so the
--ground-* options pick convex-hull faces per object and instance,
with part transformations (--rotate-x/y) applied.
- Drop --center-on-bed, the --lay-flat alias and MeshOrient; make
--ground-largest-face a coBool.
- Parse --ground-face-normal and --ground-face-point strictly. A point
that only some objects contain grounds those and leaves the others.
- Fold in --inspect-mesh from #14603, reporting the same planes.
- Tests in tests/libslic3r/test_lay_on_face.cpp: bounding boxes before
and after, rotate then ground, two objects, and a ribbed part whose
parallel inner faces outsum its base.
* CLI --inspect-mesh, --ground-face-*: reject missing input and empty values
- Without an input file or --load-assemble-list, --inspect-mesh printed
nothing and exited 0. Reject it up front with CLI_INVALID_PARAMS.
- An explicit empty --ground-face-normal or --ground-face-point was
silently ignored. Only options given on the command line reach the
transforms loop, so an empty value now fails the strict parse like any
other malformed value.
Merge origin/main (00429da739) into belt-printer.
Conflicts resolved:
- src/CMakeLists.txt: keep both wxInspector workarounds.
- GCodeProcessor.cpp: keep the belt compare_pos / z_for_height lines.
- PrintObjectSlice.cpp: the belt bbox-Z guard also covers main's
printable_region_ids bookkeeping.
- TreeSupport.cpp: the belt-floor check runs before main's PendingNode
queueing.
- Tab.hpp: keep the belt fields, drop the removed upload description
fields.
- tests/libslic3r/CMakeLists.txt: keep both test files.
Also included:
- eSUN PLA belt presets declare their own filament_id (OFkrxQC4) and
scripts/filament_id_snapshot.json is regenerated, as main's filament_id
check requires.
- Custom.json version bumped to 02.04.00.05 so the belt entries reach
existing installs.
- Fix the ambiguous WithinRel call in the belt apron width test, which
otherwise breaks the fff_print build.
BeltGCodeWriter subclassed GCodeWriter and overrode seven methods, five of them
by copying the base body and changing the transform. The base writer already
carried an axis remap and already branched at each of its seven
coordinate-emission decisions; the subclass did the same branching with a
different transform, and the two copies had begun to drift.
Replace the inheritance with a strategy object owned by GCodeWriter:
CartesianKinematics to_machine = the existing apply_axis_remap; today's base
behaviour, moved rather than changed.
BeltKinematics to_machine = MachineFrameTransform o axis_remap o
BeltBackTransform, plus a world_coordinates variant for
the PA calibration generators.
New: src/libslic3r/GCode/MachineKinematics.{hpp,cpp}, GCode/BeltKinematics.{hpp,cpp}
Deleted: src/libslic3r/BeltGCodeWriter.{hpp,cpp} (341 lines)
Points worth a reviewer's attention:
* The predicate is must_emit_all_axes(), not couples_axes(). The base returns
true for any non-identity remap, including pure permutations that do not
physically couple axes, so the question is "must every axis word be
emitted", not a statement about kinematics.
* Every per-site word-omission branch is preserved. The base deliberately
emits X/Y only, or Z only, or drops Z when its quantised value is unchanged.
The strategy changes which transform applies, never whether words are
omitted.
* set_kinematics() replays the configured remap and build volume onto a newly
installed strategy, because BeltGCode::init_belt_writer runs before
GCode.cpp calls set_axis_remap/set_build_volume_max.
* uses_pointwise_travel_speed() preserves a pre-existing divergence rather
than introducing one: the base travel_to_xyz emits the raw configured travel
speed in its final branch, ignoring the first-layer value computed at the
top, whereas the belt path used the first-layer-aware value throughout. Both
are kept. Unifying them changes feedrates and belongs in its own change.
* The [BELT-DEBUG] block is deleted; it rate-limited itself with a
function-local static thread_local in the hot emission path, and this is the
commit that would otherwise have moved it into shared code.
This commit is intended to preserve existing export output. That is reviewed by
construction -- each emission site keeps its own omission branch and each policy
divergence is preserved -- and is NOT verified against a G-code diff corpus.
Building that corpus is the outstanding work here.
Two API-equivalence exceptions, neither reachable by any caller today:
* Belt kinematics with no plane pointer installed, m_is_first_layer true,
initial and normal travel speeds differing, travel_to_xyz() reaching its
final branch: the old belt writer selected the initial-layer speed, the new
writer selects the normal travel speed. The pending-lift and XY-only
branches keep their previous selection.
* Belt kinematics installed without set_force_normal_lift(true) and a
non-normal lift requested: the old belt writer forced a normal lift, the new
writer can take the slope branch.
The PA-pattern generator reaches the writer through explicit travel_to_z() /
travel_to_xy(), not travel_to_xyz() or the lazy/eager lift paths, and normal
belt export installs both the plane and the forced-normal-lift policy, so
neither exception changes output produced today. They are recorded because a
future caller could reach them.
tests/fff_print/test_gcodewriter.cpp was also not compiling before this branch:
it called writer.to_machine_coords(), a method that existed only on
BeltGCodeWriter. It never surfaced because the build targets OrcaSlicer, not
all, and BUILD_TESTS defaults to OFF, so that translation unit was outside every
compile path. Fixed here; the existing 30-degree coordinate assertions are kept
verbatim as the best available regression net.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
SLIC3R_CAD=OFF now builds without SolveSpace or OCCT's ModelingAlgorithms
module, leaving the dependency set identical to upstream's, and the Design
tab's mate connector preference no longer appears in builds without the tab.
When the deps prefix and the project disagree about the option, the configure
fails naming the cause, rather than failing at link time or at first launch
on Windows. The Windows packaging step stages exactly the toolkits libslic3r
links.
* Add caching system for presets
* Removing user\bundle serialization and keeping it only for system presets
* Integrate caching into WebGuideDialog which speeds up time of SetupWizzard and PrinterSelection dialog
* Add CI\CD step to prepare cache file in ahead of time so user does not need to wait
* Add partial cache generation when only one of the vendros is changed to speed up recalculation time
* Handle corrupted files
* Add cache to GuideDialog as previos version didn't work as expected
* Add inspecting tool and fix CI cache generation
* Generate cache per vendor
* Simplify code by mergin it in PresetBundle
* Simplify code a bit more
* Add cereal serialize() to VendorProfile, PrinterModel, Preset, and Semver
* Remove CachedPrinterModel/VendorProfile/Preset mirror structs from VendorCache
* Fix use-after-free in CallAfter lambda; replace raw thread pointer with unique_ptr
* Use get_vendor_cache_key() to match cache keys written by the app
* Remove BOM added by VSC
* Skip invalid vendors
* Remove leftover cache file
* Fix build for windows arm64
* Revert json cache back
* Update check for stale cache
* Serealize all value fields for Preset class to minimize regression later
* Minimize field duplication by moving Cache thing into PresetBundle
* Add tests for Cache system
* Add a bit more tests
* Merge branch 'main' into feature/cache_profiles_and_optimize_loading_speed
* Rvert from per-verndor to single cache file
Replace N per-vendor .cache files with a single system_presets.cache
that holds all vendors and presets in one serialized blob.
Cache load is now all-or-nothing: on hit all vendors are applied from
the bundle (sub-second); on miss all vendors are parsed from JSON and
a fresh bundle is written to the user cache dir.
Invalidation is driven by bundle_key - a sorted concatenation of all
vendor JSON version strings. Any vendor update invalidates the whole
cache and triggers re-parse on next launch.
Guide wizard (WebGuideDialog) loads the bundled cache into a plain
PresetBundle instead of a separate VendorGuideData struct, removing
the duplicate data model.
generate_system_cache simplified from a per-vendor loop to a single
save_system_presets_cache() call producing one output file.
* Transfer all Preset fields from cache via move assignmet
apply_vendor_preset_group was copying fields manually and missed
bundle_id, user_id, base_id, sync_info, updated_time, key_values,
ini_str. Replace field-by-field copy with move assignment of the
fully-deserialized Preset, then restore the vendor pointer which
is excluded from serialization.
* Ignore cache for future
* Remove not used files
* Ship one preset cache per vendor in place of the profile JSONs
Each vendor's system presets serialize into a single <vendor>.opc built at
package time, and a shipped build carries that file alone — the profile JSON
and its sub-file tree are pruned. The vendor loader, the setup wizard's profile
list and the resource installer all read a vendor through its cache, falling
back to parsing whenever one is absent, stale or unreadable, so the cache stays
an optimization and never a source of truth. Caches hold presets in source form
and resolve inheritance at load, through the same code the JSON path uses.
* Make the preset cache self-describing and load each vendor from the system folder alone
The cached DynamicPrintConfig is keyed by name, through a per-file dictionary of the
distinct opt_keys, the type each was written as, and the distinct enum value names,
instead of by serialization_key_ordinal — a position assigned by declaration order at
static init, where inserting one option shifts every later ordinal and the lookup then
succeeds on the wrong option. Because a name-keyed payload drops the options this build
cannot place rather than being rejected wholesale, the schema fingerprint goes, and with
it the two fallbacks that existed only because an installed cache died on every app
upgrade: the second lookup tier into resources/profiles and the parse fallback to the
same place. A vendor is loaded from <data_dir>/system/ and nowhere else, as on main —
which is what makes the app write its .opc files there again.
* Simplify the preset cache internals after review
* Use the shared temp-dir helper in the preset bundle loading test
* Bound stamp string reads in the preset cache
* Speed up the setup wizard with a profile-data cache
The wizard's per-vendor fast path threw on vendors present only in
resources, falling back to a ~29 s raw JSON scan on every open. Each
vendor now loads from the directory it was found in, and the derived
model/machine/filament/process catalog is cached whole in
<data_dir>/cache/wizard_profile_data.json, stamped by each vendor's
name and version - a fresh cache makes an open one file read, with no
bundle built and no presets installed (~0.2 s vs ~2 s).
* Remove debug SVG dump from a geometry test
* Move the per-vendor cache file format into PresetCacheFormat
* Move the vendor install helpers from PresetBundle into Utils
* rename
* fix flatpak
* change cache version to 1
---------
Co-authored-by: SoftFever <softfeverever@gmail.com>
Review request on PR #15238: "Place CAD-related files (e.g. CadDocument/
GeometryEngine) into a separate folder."
src/libslic3r/CAD/ the kernel — CadDocument, GeometryEngine, the four
Sketch* units, SketchSolver, ThreadStandards
src/slic3r/GUI/CAD/ the tab — DesignPanel, DesignCanvas, DesignSketchTool,
SketchInlineEditor, McpControl, generated DesignOffer
Pure relocation: no line of logic changes. Two include rewrites follow from it —
files that moved re-spell their own neighbours against src/ (already on the
include path), and files that did not move pick up the new folder. docs and
docs/ux/mockups/gen_offer_table.py follow the same paths.
Verified: libslic3r, libslic3r_gui and libslic3r_tests all build, CAD suite green
at 2518 assertions in 194 test cases, and the sibling fork builds identically —
17 shared sources still byte-identical, 8 diverging by their expected counts.
Review request on PR #15238: "Move the SLVS to deps if the source code remains
unmodified. If any changes were made to the source code, move it to deps_src."
It is unmodified — all 20 files under src/libslic3r/slvs were byte-identical to
JacobStoren/SolveSpaceLib@4d87045, the extraction of solvespace.com's libslvs.
So deps/ it is, fetched by hash like every other dependency.
Only the CMakeLists is ours: upstream's builds a demo executable and installs
nothing, so deps/SLVS/CMakeLists.txt.in replaces it via PATCH_COMMAND — the same
shape deps/OpenCSG already uses. The public header keeps its spelling, so
SketchSolver.cpp still says `#include <slvs.h>` and needs no edit.
The CI deps cache is keyed on hashFiles('deps/**'), so it rebuilds itself.
Verified: dep_SLVS builds and installs, libslic3r links against SLVS::slvs, and
the CAD suite is unchanged at 2518 assertions in 194 test cases.
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.
Brings the belt-printer work up to date with 591 upstream commits.
Conflict resolutions (12 files, 42 hunks):
- GCode.cpp: adopted upstream's per-filament/per-nozzle config refactor
(get_filament_config_index, NOZZLE_CONFIG), the extracted
generate_timelapse_gcode + farthest-point timelapse, and the
ConfigOptionFloatsNullable calibration options. Re-applied the belt
hooks on top: init_belt_writer / axis remap / FirstLayerPlane setup,
on_set_origin, the belt-corrected calib_z for the volumetric speed
tower, and path_on_first_layer (belt's per-path first-layer test) in
place of upstream's layer-index on_first_layer() in the acceleration,
jerk and overhang-detection paths. Swept upstream's new m_writer.
uses to m_writer-> since belt holds the writer by unique_ptr.
- interpolate_value_across_layers: kept upstream's banded stepping and
belt's object-Z-span ratio; dropped upstream's duplicate ratio decl.
- Plater.cpp: took upstream's guarded add_model(...) early-returns and
the VFA vfa_layer_height plumbing; kept the belt temp-tower path,
_calib_apply_belt_mode and belt_calib_flip_ringing_tower. Dropped the
VFA "cut upper" block, superseded upstream by model scaling.
- Brim.cpp: upstream's ObjectInstanceID-keyed brimAreaMap, keeping the
belt early-return.
- 3DScene.cpp: kept both the belt build-plate tilt up_direction and
upstream's per-extruder printable-height shading.
- GCodeViewer.cpp: kept upstream's dim-previous-layers setup and belt's
exemption from the same-result early return.
- TreeSupport.cpp: upstream's >= 0 roof-layer fix inside belt's
belt-floor branch.
- calib.cpp / GCode.hpp / GCodeWriter.{cpp,hpp} / Print.hpp: upstream's
additions adapted to belt's pointer-held writer and helpers.
- Custom.json: kept profile version 02.04.00.03 (belt) over upstream's
02.04.00.01; both bumped from 02.04.00.00.
Building this tree needs the wxInspector dependency, which upstream
added in the interim (python3 and wxWidgets 3.3.2 were already present
in the shared deps prefix).