ensure_belt_purge_tower only ever looked at the current plate and kept a single
prism, so the other plates had no tower and switching plates moved the one
prism around. Every plate is now planned on its own: a prism that lies on no
plate is stale, a plate whose prism matches its recorded inputs is left alone,
the rest are deleted and recreated, highest index first.
Plater::set_bed_shape read the belt keys from the plater's own config, which
never carries them, so the branch that tilted the bed model, drew the slicing
arrow and plane and switched the build volume to belt mode never ran. The
Prepare view shows the bed as the slicing pipeline treats it, flat; the
gravity arrow from build_plate_tilt stays, as does the preview's belt view,
which takes its angle from the G-code header.
Drops the [BELT-DEBUG], [BELTRACE], [BELT-CALIB] and [BELT-PREVIEW] log
lines, the SLIC3R_BELT_DIAGNOSTIC_LOG blocks, and the counters and
temporaries that existed only to feed them. Six of the purge tower lines
logged at warning level, which is Orca's default, on every plan. Raised in
Hanif Koh's review of #14394.
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.
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.
# Description
Each plate keeps a registry of the instances it holds
(`PartPlate::obj_to_instance_set`). The plate's filament list
(`get_extruders`), its wipe tower preview and position clamp, the object
list grouping and the saved project's per-plate instance list all read
it. Two paths left it stale:
* `Plater::increase_instances` (the `+` key / toolbar) adds the copy to
the model but never registers it with any plate.
* `GLCanvas3D::do_move` (drag release and arrow keys) ended with
`notify_instance_update(-1, 0)`, so only instance 0 of each selected
object was re-registered. Rotate, scale and mirror already notify every
instance.
So a copy created with `+` and dragged onto another plate stayed unknown
to that plate: the project saved afterwards listed it on no plate, and a
multi-filament copy moved onto a single-filament plate drew no wipe
tower there and never got its tower position clamped. The Print side
selects instances by geometry, so the plate still sliced, which is why
this went unnoticed.
This PR
* registers new copies with their plate at creation;
* has `do_move` notify exactly the instances it moved (every instance of
the object when a part was moved in Volume mode), rather than instance 0
or all instances - notifying an instance that stayed put invalidates its
plate's slice result, so `(-1, -1)` as used by rotate would have
un-sliced every plate holding a sibling copy;
* drops the registry entry again when `decrease_instances` removes a
copy.
A second commit finishes the switch #15532 started with
`contain_any_instance_totally()`: `get_extruders_without_support()`,
`check_single_extruder_mixed_filament_risk()` and
`check_compatible_of_nozzle_and_filament()` still tested instance 0
only, so an object whose copy - not its original - sits on the plate was
skipped by all three.
No new options, no format change. The `is_new` flag is deliberately not
passed for the copies: a copy landing on a spiral-vase plate gets the
same "apply spiral mode settings?" prompt a dragged instance gets,
instead of a silent rewrite of the object's settings.
# Screenshots/Recordings/Graphs
Before:
<img width="1920" height="1080" alt="05-moved"
src="https://github.com/user-attachments/assets/3cf9f5a9-1a4e-41e8-8c57-578f849d8c29"
/>
After:
<img width="1920" height="1080" alt="05-moved"
src="https://github.com/user-attachments/assets/1b801a7e-b7cd-4ffb-bd1d-b701f90dade6"
/>
## Tests
Re-run after the rebase, both binaries driven through the same headless
harness (Xvfb 1920x1080, llvmpipe) on the same fixture: `cubeA`
(filament 1) alone on plate 1, `cubeB` (a two-part object, filaments 2
and 1) alone on plate 2, so plate 1 shows no wipe tower at load. Select
the plate-2 object, press `+`, walk the copy onto plate 1 with 36 x Left
(10 mm per press, one `do_move` each), save, slice plate 1.
Before is main `8af92214d0` - i.e. with #15532's
`contain_any_instance_totally()` already in place, so the only
difference is this PR.
* **Before:** the saved `model_settings.config` lists plate 1 with
`cubeA` only and plate 2 with `cubeB` instance 0. The copy (instance 1)
is listed **on no plate at all**, and plate 1 draws no wipe tower even
though a two-filament object is sitting on it.
* **After:** plate 1 lists `cubeA` **and** `cubeB` instance 1; plate 2
still lists instance 0. The plate-1 tower preview appears, and slicing
plate 1 succeeds with the tower actually generated - the filament panel
reports 1.10 m / 0.48 m in its Tower column over 51 filament changes,
and the G-code carries `EXCLUDE_OBJECT_END NAME=cubeB.stl_id_1_copy_0`.
Same camera and fixture on both runs, so the screenshots above are
directly comparable.
An instance added with "+" was never registered with the plate it landed on,
and moving an instance only re-registered instance 0 of its object, so a copy
dragged onto another plate stayed unknown to that plate's registry. The
plate's filament list, its wipe tower preview and the position clamp all read
that registry, so a multi-filament copy moved onto a single-filament plate
drew no tower there and its tower position was never clamped.
Register new copies at creation, notify exactly the instances a move changed
(every instance of the object when one of its parts moved), and drop the
registry entry when a copy is removed again.
Every edit makes the precedence the compiler already applies explicit. None
of them regroups an expression, so behavior is unchanged at all eight sites.
Strip parentheses and whitespace from the diff and the token stream matches.
GCodeProcessor.cpp:1472 tests == where the symmetric clause below tests !=,
which reads like a typo and is not one. A comment now explains why.
OrcaSlicer.cpp:4760 was the only judgment call. Its leading !is_seq_print is
bare while both operands are parenthesized, so the written form matches what
the compiler does. Kept rather than guessed at.
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
Orca content-addresses every system filament, Bambu's included, but a printer,
its AMS and its vendor's cloud know only that vendor's own catalog ids. The
printer agent now translates between the two: outbound MQTT and FTP traffic, the
AMS mapping sent with a print job, and the ids written into a 3mf bound for the
printer all leave in the printer's own ids, while status messages, loaded
projects and SD-card prints arrive in Orca's. An id with no mapping passes
through unchanged, and an agent whose printers already speak Orca's ids
translates nothing at all.
Bambu's map is generated from BambuStudio's own shipped bundle; a missing or
unreadable file leaves every lookup an identity rather than taking the app down.
The profile check validates the map's shape, and profile CI now runs on the paths
that can change it. docs/HLSD/filament_id.md records the places the map
deliberately does not reach.
* Fix incorrect early exit for CLI mode no-support preventing parameters from being read
* Use PartPlate's m_height to allow CLI to perform proper BuildVolume check
* Add safeguard against extruder_pintable_heights and extruder_areas vector size mismatch
* Preserve printable_height precision in PartPlate/PartPlateList
* Fixed multiple BuildVolume warning issue, and keep check_outside diff minimal
* build: enable /Zc:lambda for MSVC
MSVC keeps its legacy lambda processor under /std:c++17, which rejects
reading a constexpr constant inside a lambda that does not capture it
(C3493). No other compiler requires that capture, and clang reports it as
an unused one, so the two cannot both be satisfied without the flag.
/Zc:lambda selects the conforming lambda parser that clang and GCC
already use. It is implied by /std:c++20 and /permissive-, so it is only
needed while we are on C++17. clang-cl is conforming already and does not
take the flag.
It requires VS2019 16.8, so build_release_vs.bat now says 16.8+.
* build: clear 237 unused lambda capture warnings
236 captures across 81 files, 142 of them `this`. Removing an unused
capture changes no behavior; clang does not report a capture whose type
has a non-trivial destructor, so nothing held only to extend an object's
lifetime is in this set.
Nine of them are the second half of the warning, "is not required to be
captured for this use", where the capture is a const or constexpr value
the body does read. Those depend on the /Zc:lambda change in the previous
commit. One of them, in FillRectilinear.cpp, had been worked around with
an #ifndef __APPLE__ guard around the capture list, which is now gone.
GUI_ObjectTableSettings.cpp captured its reset button only to read it
inside #ifdef __WXOSX_MAC__. That branch now takes the button from the
event it is already handling.
* build: fail configure on MSVC older than 19.28 instead of dropping /Zc:lambda
cl.exe answers an unrecognized /Zc: sub-option with warning D9002 and keeps
going, so on VS2019 before 16.8 the flag is silently ignored and the build
instead dies with C3493 in FillRectilinear.cpp, nowhere near the cause.
* fix: delete three locals that are now unused
Their only remaining use was the lambda capture this branch removed. The
Clang builds set -Wno-unused-variable, so the build never flagged them.
---------
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
* build: remove std::move that blocks copy elision
std::move wrapped around a temporary, or around a local being returned,
stops the compiler constructing it in place. Each edit is the fix clang
suggests, which is to delete the std::move call and keep its argument.
Three of the 39 sites save a move, the two return std::move(local) in
Print.cpp and TreeSupport.cpp:2749. The rest are equivalent either way
and match how the codebase already writes this elsewhere.
Clears 39 -Wpessimizing-move warnings.
* build: drop null checks on references and this
A reference cannot be bound to null and this cannot be null, so the
compiler folds these conditions to true and drops the guard. Seven are
if (&bitmap && bitmap.IsOk()), where IsOk() already does the work; two
test this directly. The guarded code runs either way, so removing the
dead operand changes nothing.
Clears 11 -Wundefined-bool-conversion warnings.
* Keep mixed-color filaments intact when the extruder count changes
The extruder-count spinner resized the filament arrays in bulk at the tail,
which is where mixed-color slots live, so a new filament landed behind the
mix and the sidebar skipped a slot number. It now adds and removes one slot
at a time through the same calls the sidebar's +/- buttons use, so a new
slot opens ahead of the mixed tail and a removal renumbers object filament
ids, painted facets, custom g-code and mixed components rather than
clamping them away.
Drops the vector overload of set_num_filaments(), which this leaves without
callers.
Call update_dynamic_filament_list() alongside update_mixed_filament_list() in two places: after editing a mixed filament slot and when the filament count doesn't change (e.g., adding a mixed/virtual slot). This ensures per-feature filament lists reflect the updated blended colour and type without requiring a full filament count change.
The sidebar Mixed Filament list, the extruder icons, the color painting
gizmo and the canvas filament bar now show the same bottom-to-top fade the
Edit Mixed Filament preview shows, custom gradient curves included, instead
of a horizontal fade between the two component colours. Ordinary and vendor
multi-colour filaments are drawn exactly as before.
Mixed-colour slots are virtual and never flushed. Guard auto_calc_flushing_volumes_internal against them as BambuStudio does, and make the flushing dialog's default matrix and the sidebar 'modified' comparison physical-only so the untouched mixed rows no longer count as a user edit and the Re-calculate result matches the physical-only table.