Follow-up to #16195 and the review discussion on #14394 (yw4z's note
about the third column on the *Belt tilt* row). Removes the belt options
that are redundant or unused before the branch ships, so they never need
compatibility handling after a release, and fixes supports under a
leading overhang. Every removed key is on `handle_legacy()`'s ignore
list, so existing profiles and 3MFs load silently.
## Removed
- **`belt_slice_rotation_global`**, **`preslice_remap_global`**,
**`belt_preslice_global`** (*Global mesh transforms*) and
**`gcode_back_transform`** — the global mode and the back-transform are
what belt printing is; they are presumed on wherever the flags were
consulted (`PrintObjectSlice`, `BeltBackTransform`, `BeltGCode`,
`Print::process`, `PrintApply`, `GCodeViewer`). The *Belt tilt* row is
axis + angle only; the three `fdm_belt_common.json` drop the keys.
- **`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.
The forward transform is the rotation.
- **`belt_support_z_offset_mode`** and **`belt_support_floor_mode`** —
the first was never read by a generator; the second's only shipped value
(*Generator only*) is now the behaviour.
- **`first_layer_plane`**, **`first_layer_plane_offset`**,
**`first_layer_plane_thickness`** and `FirstLayerPlane.{cpp,hpp}` — the
first-layer band is measured from the belt surface and is one first
layer height thick.
- `belt_brim_instances_compatible()` and its validation warning:
instances along the belt get their brim.
## Supports under a leading overhang (the clipping at the object's local
Z = 0)
The slicing frame of a belt object started at its lowest vertex, but the
belt under the leading end of an overhang lies below that, so no
generator could reach it: normal supports stopped at the object's lowest
layer, and both tree generators carried extension hacks sized from the
pre-rotation bbox and capped at global Z = 0 (right only for the
trailing half of the belt). The frame now starts at the lowest
belt-floor point under the footprint, less a 10 mm margin along the belt
for the base of a support column, and the extensions are gone:
- **Normal supports** run in the object frame and get the global belt Z
offset shifted onto the result (as organic already did). With the offset
on the object layers, a top contact at negative Z turned the
intermediate-layer count negative and the generator allocated layers
until the kernel killed it — any overhang in the leading half of the
belt did this. The first-layer flange expansion is skipped on a belt
(the first support layer is the leading tip, not a flange).
- **Classic tree** nodes keep dropping until their whole circle is in
the belt, so a branch tapers to a tip on the belt instead of stopping a
radius above it.
- **Organic**: the belt is no longer a support blocker. A blocker is a
collision, and a branch descending onto one slides off it, down the belt
and ahead of the part; the belt is where branches end, which the
per-layer floor clipping already does.
Regression test *Belt supports reach the belt under a leading overhang*:
a cube with a fin whose underside is parallel to the layers, 20 mm ahead
of the cube and up to 41 mm of slicing Z above the belt, for normal,
organic and classic tree supports; the lowest support layer must sit on
the belt beneath its own lines.
The belt object height (the layer range) is now estimated from the box
of the mesh as placed on the bed. `raw_bounding_box()` has the
instance's Z offset removed, which was harmless for the old
rotated-extent estimate but not for one anchored at the belt floor (a
point's rotated z and the floor under it move in opposite directions
under a Z shift): with the first version of this change every part came
out as a wedge, sliced only up to its diagonal, in the GUI and CLI
alike. Caught by a GUI test pass; the leading-overhang test now also
checks that the whole part is sliced.
## Belt brim after the parallel support step
`belt_brim_obstacles()` reads every object's layers and support layers,
which another object's support step rebuilds (and now shifts) at the
same time. The brim is generated sequentially once the parallel step is
over (`PrintObject::generate_belt_brim()`). This is the race behind the
Windows arm64 segfault in *Belt brim of each object precedes its
perimeters on its own filament*.
## UI
- *Belt tilt* is two rows: the angle (Advanced) and the axis (Developer;
a profile-level kinematics choice). A shared line is shown by its first
option's mode, so they cannot share one.
- *Machine frame transforms* is five single-option rows (G-code remap X
/ Y / Z, Decouple machine-frame tilt, Machine-frame tilt angle — the
angle row only appears when decoupled) instead of two multi-column
lines; the remap fields got full labels since they stand alone now.
- The gravity indicator on the bed is a plain line along the up
direction (no cone, 60 % of the axes' length), per yw4z.
- The *Show raw G-code (belt only)* legend/canvas toggle and its `B`
shortcut are gone; the preview is the designed view.
Also carries the two-line `phong.fs` fix from #16226 (merges as a
no-op).
## Verification
- `libslic3r_tests` 1116 passed (92 648 assertions); `fff_print_tests`
351 passed (561 696 assertions).
- `scripts/clang_tidy_diff.py --base upstream/belt-printer`: no
findings.
- `scripts/orca_profile_tool.py check`: no profile references a removed
key.
- GUI target builds; a scripted GUI pass (xdotool) checked the settings
groups in every mode, slicing, export, instances, the purge tower,
calibration dialogs, the wizard, printer switching and 3MF round-trip.
The wiki pages (OrcaSlicer/OrcaSlicer_WIKI#374) get a follow-up dropping
the removed sections once this is in.
The belt object height is estimated from a bounding box swept through
the tilt rotation. raw_bounding_box() has the instance's Z offset
removed, which did not matter while the estimate was the box's rotated
Z extent (a Z shift moves every corner alike), but the frame now starts
at the lowest belt-floor point under the footprint, and a point's
rotated z and the floor under it move in opposite directions under a Z
shift: the offset box under-estimated the height by twice the object's
height above the bed, so the layers stopped at the part's diagonal and
every part came out as a wedge (GUI and CLI alike; the unit tests never
checked the top). Use the box of the mesh in the frame it is sliced in
(trafo_centered(), Z as placed on the bed), and have the leading
overhang test check that the whole part is sliced.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The slicing frame of a belt object started at its lowest vertex, but the
belt under the leading end of an overhang lies below that, by the
overhang's length times the tilt's shear. Every support generator works
in layers at z >= 0, so none of them could reach it: normal supports
stopped at the object's own lowest layer, and the two tree generators
each carried a stack of hacks to extend themselves below it (a post-hoc
copy of the lowest base area in TreeSupport, "virtual belt raft layers"
in TreeSupport3D/TreeModelVolumes), sized from the pre-rotation bbox
and capped at global z = 0, which is only right for the trailing half
of the belt.
Start the frame at the lowest belt-floor point under the footprint
instead, less a 10 mm margin along the belt for the base of a support
column (BeltSliceStrategy::apply_preslice_transforms and
BeltTransformPipeline::compute_belt_height_and_floor agree on it). The
layers between it and the first vertex come out empty, which belt
slicing already tolerates, and the generators need no extension at all:
- normal supports: the generator anchors its layer grid at the frame
origin, so run it in the object frame and shift the global belt Z
offset onto the result afterwards, as organic supports already did.
With the offset on the object layers a top contact at negative z
turned the intermediate-layer count negative and the generator
allocated layers until the kernel killed it (any overhang in the
leading half of the belt). Drop the first-layer flange expansion on a
belt: the first support layer is the leading tip of the support, not
a flange, and inflating it put lines in the air ahead of the belt.
- classic tree: a node now keeps dropping until its whole circle is in
the belt, so the branch tapers to a tip on the belt instead of
stopping, a radius above it, when its centre crosses.
- organic: the belt is no longer a support blocker. A blocker is a
collision, and a branch descending onto one slides off it, down the
tilted belt and ahead of the part; the belt is where branches end,
which the per-layer m_belt_floor clipping already does.
The belt brim is generated after the parallel support step instead of
inside it: belt_brim_obstacles() reads every object's layers and support
layers, which another object's support step rebuilds (and, now, shifts)
at the same time. This is the race behind the Windows arm64 segfault
in "Belt brim of each object precedes its perimeters on its own
filament".
Also: the belt tilt axis moves to Developer mode as its own row (a
shared line is shown by its first option's mode), first_layer_plane
band thickness, belt_support_floor_mode, belt_preslice_global and
gcode_back_transform are retired and presumed on, the gravity arrow is
a plain line along the up direction, and the "Show raw G-code (belt
only)" preview toggle is gone.
Regression test: "Belt supports reach the belt under a leading
overhang" slices a cube with a fin whose underside is parallel to the
layers, 20 mm ahead of the cube and up to 41 mm of slicing Z above the
belt, for normal, organic and classic tree supports, and checks that
the lowest support layer sits on the belt beneath its own lines.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
* perf: write post-processed G-code without a per-line copy
* perf: size the post-process line map from the first pass
* test: line ends of the exported G-code
* test: include the headers the line-ends test and gcode() helper use
The XY, XZ and YZ planes no longer cross through the bed. Each is a small square in its axis
colour, set off from the axes into the corner that faces the default front view, with its name
written in the plane. Dash-dot axes run between the squares and replace the bed's axis triad
while they show. Hovering a plane greys it and selecting one makes it solid, and picking a
solid face now clears a previously picked plane.
Grabbing an arrow anywhere along its length snapped the body's centre to that point as soon
as the mouse moved. The body now moves by how far the cursor travels from where the arrow
was grabbed, including when it is grabbed while looking straight down the axis.
The XY/XZ/YZ planes are cut along each other and drawn back to front, with
lines along every crossing, and their fills are strong enough for the order
to show. Before, they blended into one grey smear and were too pale to work with.
tests/fff_print/test_print.cpp includes <Windows.h>, so an unqualified Polygon
in the new TreeModelVolumes test is ambiguous with GDI's Polygon() and fails
the Windows x64 and arm64 builds on belt-printer.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
In the Design tab the button always swung the camera to the plate view and framed the whole
bed, whatever was selected: the tab's picks and sketches are neither the canvas's selection
nor its volumes. It now frames the selected faces, body, edges, vertex, sketch region or
sketch entities, and with nothing selected everything on show — the visible bodies, the
feature preview and the sketches — keeping the current view direction. An empty tab still
frames the bed as before.
Code review items (raistlin7447):
1. PrintObject::slice() zeroes m_belt_min_z, m_belt_global_z_offset and
m_belt_global_xy_correction before slicing. They were only written in belt
mode, so a project switched to a normal printer, or whose tilt axis was set
to None, kept the old offsets and shifted the adaptive infill octree and the
organic support layers by them.
2. TreeModelVolumes shifts the support blockers into the raft-offset index
space; a test now pins the index the blocker lands on.
3. The final-alignment clamp in libnest2d is opt-in (NfpPConfig::clamp_to_bin)
and arrange sets it for belt printers only. Printers with an off-centre
best_object_pos keep their alignment; a flat-bed test pins that.
4. The preview's belt view follows the loaded G-code, not the selected printer:
GCodeProcessor carries the file's belt keys (and, for a belt file, its bed)
into export_config_for_render(), and GCodeViewer enables the belt view from
the header tilt.
5. belt_shift_layer_grid() also shifts the cached belt floor and the global Z
offset, so a support-only or brim-only change after the purge-prism snap
matches a fresh slice.
6. update_print_fff_config() resets raft_layers and draft_shield on a belt
printer instead of only greying out the fields Print::validate() rejects.
7. GCodeWriter takes a first-layer point test instead of the FirstLayerPlane;
GCode installs one that measures from the belt surface, like its
extrusions, so the first-layer travel speed and the second-layer
temperature change no longer depend on the gcode_remap_* convention.
8. belt_brim_clip_leading_edge() is exported and called by both the generator
and the test.
9. Both phong.vs shaders use slope.up_direction for the overhang highlight.
The pre-slice and G-code axis remaps are gated on belt_printer through
BeltTransformPipeline::axis_remap_enabled(), so belt keys left in a profile
cannot change a non-belt print.
Tests requested in the review: belt-only keys at non-default values leave
non-belt G-code unchanged; switching a sliced project from belt to non-belt
(and tilt axis None) matches a fresh slice; a support-only change on a belt
purge print matches a fresh slice; non-belt start G-code moves keep the
first-layer Z in the processor; the belt brim's segment count catches a band
emitted twice.
The belt-to-non-belt test exposed an unrelated gap: invalidate_step(posSlice)
re-invalidated posSupportMaterial but not posSimplifySupportPath, so after
any re-slice the regenerated support paths were exported unsimplified.
posSimplifySupportPath is now in that list.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The clang-tidy job on #14394 fails on 129 misc-include-cleaner findings:
the belt sources and tests use std::, Eigen, Point/PrintConfig and
BeltBrim symbols without including the header that provides them, which
only compiled because the precompiled header supplied it. Every include
the job names is added, in each file's existing include style ("../" in
the GCode/ and Support/ subdirectories, quoted libslic3r/ paths in the
GUI and tests). No code changes.
Verified with scripts/clang_tidy_diff.py -p build-tidy --base eb5b9a77b9
(SLIC3R_PCH=OFF compile database, clang-tidy 22.1.8): no findings left.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6Kg5igmmMU2YLoK6HrsWV
Brings belt-printer up to main eb5b9a77b9. One conflict: main translates
the pressure advance test name (#16142) on the line after belt's guard that
keeps PA Line and PA Pattern off belt printers; both are kept.
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced
Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes.
clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer.
* Remove Unused Project Includes From Files With Platform-Specific Code
A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block.
* Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass
The platform-file pass treated pchheader.hpp as an ordinary header and
emptied it, and left GUI_Preview.hpp and 14 other files relying on
headers they no longer reached directly.
* Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call
* Include Headers That Files Reached Through Ones the Cleanup Removed
* Drop Includes Duplicated by the Cleanup or by Main's Own Additions
* Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
slice_single_volume_regions() sliced Precise Seam modifiers with
trafo_centered(), but a belt printer slices its layers with
trafo_sliced(): the belt rotation, any pre-slice remap and the lift off
the plate on top. On a belt print the modifier regions landed in the
unrotated frame, away from the walls they were meant to place the seam
on. Slice them with trafo_sliced(), as the support volumes and the seam
enforcers already are. It equals trafo_centered() off a belt printer.
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.
Selections are drawn as opaque faces in the selection colour with a cased outline instead of a
translucent tint over the body, so they read on a body of any colour. Selecting a Feature tree
row lights the faces that feature made rather than its whole body, which also makes fillet and
chamfer rows highlight again.
perf: skip estimating curled walls when nothing reads them
The curled extrusion estimate ran whenever a region had overhang speed on,
which is the default, but only the slowdown for curled perimeters reads the
curled lines it produces, and that slowdown is off by default. The step now
also requires a region with the slowdown on, and clears the curled lines
when it skips the estimate, so none are left from an earlier slice.
Also fixes stale fan commands due to the stale curled lines on the reused layers.
Every file that included STEP.hpp, directly or not, got namespace fs = boost::filesystem at global scope, and 29 sources and three headers relied on it without saying so. Headers now spell out boost::filesystem, and each source that uses fs declares the alias itself.
* 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.
The purge plan snaps every object onto one layer grid after the brim is built;
the per-layer brim bands follow their layers but the apron bands below the
first layer carry their own print_z and were left behind, which is why a brim
was refused next to a purge tower object. The shift now moves them too and the
combination is accepted again.
On a belt the parts print in belt order, so every colour change between parts
is a filament change. Arrange packs items in extruder order already, but it
grew the pile around its centre, so the colours ended up interleaved. A belt
print now packs from the leading end of the bed, each row filling across the
belt before the pile advances, and the objective charges an item for every
packed part of another colour it does not fully follow along the belt,
counting the tilted layers that reach cot(angle) * height past a part, so
each colour prints as one run. The direction follows the slicing rotation: a
rotation about X prints toward +Y, one about Y toward -X, and a negative angle
flips it. Packing from the edge also means the brim has to be kept on the
bed: a belt brim is printed brim_width wide for every brim type, so that much
is reserved along every edge (between parts the brims may overlap, as on any
printer).
The purge prism is regenerated from the arranged parts, flush with the far
edge of the bed, yet arrange moved it about like a part and packed parts into
the strip it comes back to. Arrange now skips the prism and reserves its strip
with a fixed virtual item, like a bed exclusion area, whenever the parts use
more than one filament.
The prism's length follows the parts plus a ramp per unit of height; with the
height at its cap that ran 100 mm past the end of a 500 mm belt and the project
could not print. The bar now stops at the plate end, and the purge planner's
existing warning reports what the shortened bar cannot absorb.
Release builds install each vendor as its preset cache alone. The
read-only preset load the CLI uses to resolve an inheriting user preset
passed allow_cache = false to keep caches from being written, which
also stopped them from being read, so every vendor fell back to JSONs
that are not installed and the CLI failed.
The flag now only gates writing: a read-only load reads caches and
writes none. The filament library is also read from its cache whenever
that is all that is installed, so a vendor updated over the air still
resolves against it.
* Refresh a CLI Project's Filament Settings From Their System Presets
The CLI loads a project's printer and process settings as the GUI does,
taking every key the project does not list as changed from the current
system preset, but it kept the stored filament values. A project saved
before a profile update then sliced with old filament values on the
command line and with the current ones in the GUI.
Every project filament that no loaded filament replaces is now resolved
by its system preset name and fed to the filament merge the up-to-date
path already uses, which keeps the keys listed in
different_settings_to_system and maps per-variant values onto the
preset's variants. This covers a plain run, --uptodate without
--uptodate-filaments, and the slots --load-filaments leaves empty. The
merge tells refreshed entries from loaded ones per entry instead of by
the global loaded-filament count, and the entries are kept in slot
order. A project filament saved under a name the presets have since
split per nozzle is resolved through the name conversion the GUI uses,
which PresetBundle now exposes.
* Check the Project Refresh Test's Result Directly
Shellcheck SC2181: test the checker's exit status in the if instead of
reading $? afterwards.