Commit Graph
4130 Commits
Author SHA1 Message Date
Hanif Koh 2f2a6bc3b5 Share the Estimated First-Layer Outline of the Wipe Tower
The preview brim, the placement margin and the pre-generation validation
warning each decided on their own whether the tower has a Type2 cone
base, reading the wall type and cone angle three different ways. The
preview's read cast the preset's enum to ConfigOptionEnum<T>, which a
preset-shaped config never holds, so the cone base was never previewed.

estimate_wipe_tower_first_layer_outline now answers that question once,
beside the footprint estimate, from the config and the resolved planner;
all three sites take the outline from it. The libslic3r case reads the
outline off a preset-shaped config, where the old cast came back empty.
2026-09-09 15:45:16 +08:00
Hanif Koh 81357695c5 Verify WipeTower Footprint at Point of Generation
The clamps and validation work from estimates. Once the tower is
generated, _make_wipe_tower re-tests the exact first-layer footprint,
brim and cone base included, against the printable area and the
exclusion zone, so an off-plate tower fails with a clear error instead
of exporting unprintable G-code. The rectangle-wall mesh footprint
learns the Type2 cone base so that check and the post-generation
validation see the real outline.

Pre-generation, validation hard-checks the body plus an explicit brim
and warns on the estimated auto brim and cone base with the existing
"may collide" strings, so the user hears about a marginal position on
the first slice rather than only at generation time.

Two fff_print fixtures that print a tower at the default position move
it onto the 200 mm test bed, as the multifilament fixtures already do:
the shipped default y of 220 is off that bed, and the backstop now says
so instead of exporting the tower.
2026-09-09 15:45:16 +08:00
Hanif Koh 2fdc16f9f2 Size a no-purge tower at the planners' idle depth
Smooth timelapse no longer charges a prime volume it does not purge. A
tower printed with no tool change is exactly the idle depth: the
stability minimum for Type2, the wrapping detection depth for Type1.
Charging a full prime_volume on top made the previewed and arranged
tower deeper than the one that is printed.

The Type2 half of "a tool change reserves a tower whatever the purge
volumes resolve to" arrives with the base commit; here it only has to
survive the planner split, since Type1 already reserves per filament.

The wipe tower filament only joins the tool ordering when there is a
tower to join, which is the has_wipe_tower() half of the guard
Print::extruders applies.
2026-09-09 15:45:16 +08:00
Hanif Koh 869805132e Add Separate Comfort Margin for Auto Placement 2026-09-09 15:45:16 +08:00
Hanif Koh 99627c8e93 Size the Footprint Estimate from the Planners
The shared estimate reserved every tower with one volume-per-purge rule
and the stability floor. Both planners do more: WipeTower (Type1) wipes
each filament's own prime volume in whole lines, one block per
adhesiveness category sized by its worst layer, rams the leaving
filament at every nozzle change, and squares a rib tower from the
planned depth; WipeTower2 (Type2) spaces its lines by
wipe_tower_extra_spacing, not the Type1-only infill gap, and its extra
flow cancels out of the depth. Both extend the ribs rather than the body
below the stability minimum, size every layer including a thinner first
one, and lay the brim in whole loops, WipeTower reporting half a spacing
of line width on top.

All of that now lives in estimate_wipe_tower_footprint, fed the planner
(resolve_wipe_tower_type mirrors Print::wipe_tower_type and the CLI's
Bambu Lab detection) and the filament ids rather than a count. Print
passes its own tool set; the PartPlate adapter derives the plate's ids
from the passed config and treats an explicit count as a floor, so the
CLI's count-only callers size per filament too. The placement clamp also
reserves a Type2 cone's base bulge, which the body box does not cover.

The planner-mirroring helpers sit beside the planners in WipeTower and
WipeTower2 so the two stay in sync; the libslic3r cases pin them to
footprints measured from generated G-code.
2026-09-09 15:45:16 +08:00
Hanif Koh 98acd687f7 Fixes for Wipe Tower Position Clamping
Validation grows the estimated body by the brim before the tower is
generated, so a tower whose brim leaves the bed is rejected up front
instead of at export. The scene reload re-clamps the stored position,
since set_default_wipe_tower_pos_for_plate does not rerun when painting
changes the filament count. The rectangle-wall footprint polygon gets its
two missing brim corners (it was a skewed quad), so the post-generation
check covers the whole brim.
2026-09-09 15:45:16 +08:00
Hanif Koh e1efec7d6c Fix review findings in the shared wipe tower estimate
A raft is not a reason to reserve a tower. Print::apply runs
normalize_fdm_2, which clears enable_prime_tower for a plate that purges
one filament unless smooth timelapse or wrapping detection is on, so a
single-filament plate with a raft prints no tower at all and the estimate
was reserving bed area for one. Drop the input; need_wipe_tower is now
exactly the two exceptions normalize_fdm_2 honours, named there so the
next reason added has to be checked against it.

The GUI preview and the validation containment check each re-derived
"is a tower printed here" from the filament count instead of reading the
estimate, so both missed the towers printed with no tool change to purge
for. They now take the answer from the footprint, which is the drift this
shared estimate exists to remove. A tower that is not printed estimates to
zero, so its hull is degenerate and every check on it passes trivially -
the containment check needs no gate of its own.

WipeTowerData::width was written only by the pre-generation estimate and
left at zero for the whole post-generation life of the Print, while its
neighbour depth held the real value. Set it from the generator in both
branches.

The plate's height scan transformed every model part's full mesh per
instance on each scene reload, discarding all but the z extent. The
cached convex hull has the same z extent.

A plate loaded from a sliced .gcode.3mf holds no objects and its filaments
live in slice_filaments_info; the config-taking get_extruders overload
returned an empty list for it, which sized the tower for a placeholder two
filaments. It now answers the way the wx overload does, without reaching
the plater.

Also drop estimate_wipe_tower_size, which has no callers.
2026-09-09 15:45:16 +08:00
Hanif Koh 8df5e5e738 Extract and Unify Wipe Tower Estimation 2026-09-09 15:45:16 +08:00
HanifKoh 8a291f9d56 Confine config import to the preset directory (#15608)
import_presets reduced each zip entry to a basename by stripping only
'/', so on Windows an entry named with '\' separators kept its
directory components and was extracted wherever they pointed. Strip
both separators, and reject any entry whose name still escapes the
extraction folder.

The preset name from the JSON and the bundle id from
bundle_structure.json were joined onto the preset directory unchecked
as well, which let either of them write outside it on every platform.
Both are now validated before anything is written.

The check is the is_path_within_root helper the 3MF importer already
had, moved to Utils so both importers share it. It treats '/' and '\'
as separators on every platform, so a bundle that would escape on one
OS is rejected on all of them.
2026-09-09 15:35:21 +08:00
ExPikaPaka 60c03e706a add alternative baking algorithm 2026-09-09 08:42:37 +02:00
harrierpigeonandClaude Opus 5 1b6fb2a81f Belt: fix first-layer speed and the slow_down_layers ramp never applying
Fixes the report in #12998 (comment 5465250754): first-layer speed and the
slow_down_layers ramp were ignored on a belt printer. The report reads as a
per-object problem, but neither applied to *any* object -- the reporter's first
part slowed down because slow_down_for_layer_cooling was on, which is
CoolingBuffer's time-per-layer mechanism, not initial_layer_speed.

FirstLayerPlane decides first-layer-ness by perpendicular distance to a plane it
derives by composing gcode_remap_* with compute_machine_z_affine(). The plane is
therefore a function of how G-code is *addressed*, not of where the belt is:
change the output axis convention and the plane moves. On MCBELT-TYPE2 the
first layer measured 86.2 mm from the plane and got effective index 431, far
past any slow_down_layers ramp.

on_first_layer(point) and effective_layer_index_for_point() now measure height
above the belt surface, using the belt description already carried in
SlicingParameters -- belt_floor_shear_factor / belt_floor_from_axis /
belt_floor_z_shift -- the same description the support generator uses. That is a
property of how the object was sliced, so no remap or back-transform can perturb
it.

Deliberately not via BeltFloorContext: its init() folds in
belt_support_floor_offset, a support-generator diagnostic, and letting that
option steer the model's first-layer speed band would be a surprising coupling
(a negative value would switch the slowdown off outright).

Preserving the existing first-layer-plane settings:

  * first_layer_plane XY/YZ/XZ keeps the FirstLayerPlane evaluator, as those are
    explicit opt-outs.
  * A non-zero first_layer_plane_offset also keeps it. The offset is a machine-Z
    shift that FirstLayerPlane converts into a perpendicular distance in the
    slicing frame; this evaluator measures along slicing Z, so there is no
    faithful translation. Deferring to the evaluator that implements the setting
    beats silently ignoring it.
  * The two thresholds stay separate, exactly as FirstLayerPlane keeps them:
    the first-layer boolean tests initial_layer_print_height, while the
    effective layer index counts bands of first_layer_plane_thickness.

Brim and coincident apron bands are emitted before m_layer is switched to their
object -- for an apron band there is no Layer at all -- so both paths publish the
belt-floor owner explicitly. Without that a brim's classification would borrow
whichever object was visited previously, making it depend on plate order.

Note that first-layer-ness drives more than speed: extrusion acceleration, jerk,
the first-layer flow ratio and eligibility for overhang speed/fan analysis all
read it, so all of them are corrected on belt printers by this change.
Classification still samples only each path's first point, as it did before.

Non-belt is unaffected by construction: belt_height_above_floor() returns false
when the belt floor is inactive and both call sites fall back to the previous
path. FirstLayerPlane stays in place for its other modes and for CoolingBuffer,
whose machine-coordinate probe is a separate outstanding bug.

Measured, MCTEST4 on MCBELT-TYPE2 (initial_layer_speed=5, slow_down_layers=40):
15 distinct feedrates with no gradient and F300 absent, becomes 70 including the
full ramp 300(5) 382(6) 465(8) 630(10) 795(13) ... Two bare cubes on a belt:
0 slow extrusions becomes 2378 across Z 32.36..95.18. The same two cubes on a
Cartesian printer keep their slow extrusions confined to Z 0.20..2.00.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:59:02 -05:00
harrierpigeonandClaude Opus 5 767db71500 Belt: fix three tree-support bugs, one of which blocked slicing entirely
1. Belt tree support could not slice at all.

layer_initialize() hardcodes layer 0's bottom_z to 0, encoding "below layer 0 is
the build plate at z = 0". True for a flat bed; false for a belt, whose virtual
support layers legitimately extend below zero. The bottom-most belt layer
therefore got height = print_z - 0 = -9.8, which reached Flow::with_height() and
threw FlowErrorNegativeFlow.

A 3DBenchy, a mushroom, an L-bracket and an extruded L all failed identically
with negative flow / return -100. Only a bare cube sliced, because its support
never reached that far down.

The bottom is now taken from the previous layer's z, and only a layer 0 whose
print_z is itself negative gets a synthesised bottom below it. Every
non-negative print_z -- every non-belt configuration -- keeps exactly the
previous 0, so this is behaviour-preserving off a belt by construction. An
earlier form used min(0., layer_z(0) - layer_height), which regressed flat beds
whenever the initial layer was thinner than the layer height.

  3DBenchy on a 45-degree belt with organic tree support: fails to slice ->
  247 support blocks / 168,596 extrusions.

2. Support generated against the belt, and against belt-tilted walls.

A plain 20mm cube on a 45-degree belt generated 86 support blocks and 46,307
support extrusions. Three causes, all gated on the belt floor being active:

  a. The build-plate tilt compensation shifted the lower layer the wrong way.
     tan(build_plate_tilt_*) carries a magnitude but no direction, and the sign
     chosen moved the lower layer away from the newly appearing material rather
     than under it, doubling the mismatch. The shift now comes from
     belt_floor_shear_factor / belt_floor_from_axis, which carry sign and axis
     exactly. Non-belt tilted beds keep the previous behaviour.
  b. Material resting on the belt was treated as unsupported. The belt surface
     is now unioned into the effective lower layer, sampled at the bottom of the
     layer -- a layer meets the belt across its thickness and print_z is the
     top. The half-plane is clipped to the layer's bounding box first: unioning
     a +/-1000mm half-plane raw with 20mm-scale geometry put a huge dynamic
     range through Clipper and left intermittent artefacts every few layers.
  c. The object's first slice can be empty on a belt (the bottom vertex is a
     sub-extrudable sliver), leaving the layer above with an empty predecessor
     even though it rests on the belt. (b) already covers that per island. What
     did need fixing is sharp-tail detection, which tests each island against
     the raw lower slices; with an empty predecessor that test is trivially true
     and every belt-contact island read as a sharp tail. It now tests against
     the same effective lower layer.

     An earlier form instead skipped the whole layer when the point of
     get_extents(curr_polys) -- the bounding box of the union of every island --
     nearest the belt was in contact. That was wrong in a way worth recording:
     one island resting on the belt could suppress overhang and sharp-tail
     detection for a separate island floating well above it. Every decision here
     is per-island.

  Cube on belt: 46,307 -> 0 support extrusions. Same cube non-belt: 0 before and
  after. Benchy on belt still 247 blocks / 168,596 extrusions and a mushroom
  111 / 82,157, so false positives are removed without suppressing true ones.

  Non-belt is unchanged by measurement, not only by the belt_ovh_active gate:
  the same mushroom sliced on a Cartesian printer before and after gives 65,866
  support extrusions and 68,717 total extrusions both times, the two G-code
  files differing in exactly one line -- the object's plate position.

3. m_anti_overhang was filled and read in different index spaces.

It is consumed in the same index space as m_layer_outlines, where object layer i
lives at num_raft_layers + i, but was filled in object-layer space. Every entry
landed num_raft_layers too low (50 for a 20mm cube at bed Y=50) and the topmost
object layers got none. The belt injection also ran before m_raft_layers was
extended, so it could not have known the offset.

The array is now shifted as a whole and the injection moved after the raft
extension. This also repairs user support blockers under a raft, which is not
belt-specific: it changes behaviour for any ordinary raft, not just the belt's
virtual one, and should be reviewed as a general fix. Measured effect on the
cube was small on its own (46,307 -> 46,334 before the other fixes) because
m_anti_overhang only feeds calculate_placable; kept as a correctness fix on its
own merits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:58:47 -05:00
harrierpigeonandClaude Opus 5 4d2c3a0af4 GCodeWriter: fix two machine-mapping bugs the extraction preserved
Both change emitted G-code, which is why they were kept out of the extraction
commit. Both are wrong only where the machine mapping is non-identity, which is
the definition of each bug.

1. Suppress lifts commanded through an unknown position.

_travel_to_z() emits full XYZ whenever the mapping must emit every axis, because
the mapping can make machine Z depend on logical X/Y, and it builds that point
from m_pos. At print start, and after any custom G-code that invalidates
position, m_pos.xy is the uninitialised origin; mapping (0, 0, z) through a
non-identity remap produces a real but wrong machine point -- for a reverse
mapping, build_vol_max, i.e. the far corner of the bed. The subsequent full-XYZ
move corrects the position, but the lift has already commanded a rapid across
the whole bed at travel speed.

Belt kinematics already guarded this; the Cartesian path did not. The guard is
now applied at all three lift sites through must_skip_lift_now(), not just the
one the extraction covered: travel_to_xyz()'s pending-lift branch,
lazy_lift(spiral_vase=true), and eager_lift(). The latter two also needed the
state fix -- both recorded m_lifted = target_lift regardless, so suppressing
only the emission would leave a later unlift() descending from a height that was
never commanded.

2. Never emit a G2/G3 arc a mapping cannot represent.

extrude_arc_to_xy() emitted G2/G3 with logical X/Y and I/J and never consulted
the mapping. There is no general fix by transforming the arc: a permutation
moves it out of the XY plane that I/J describes, a negation reverses handedness,
and the belt shear maps a circle to an ellipse that G2/G3 cannot express at all.

So supports_arc_moves() gates generation through the existing
GCode::should_disable_arc_fitting() hook, and BeltGCode's special-case override
is deleted -- belt now gets the same behaviour from the general rule instead of
its own exception.

supports_arc_moves() is m_remap_x == 0 && m_remap_y == 1, not !has_axis_remap():
an arc emits only X/Y/I/J, so a mapping that merely negates or reverses Z leaves
every emitted word untouched and keeps its arcs.

The fallback for an unrepresentable arc tessellates it into linear segments at a
0.005mm chord tolerance rather than substituting a single chord, and splits dE
proportionally across the segments. The capability check is hoisted above every
extrusion mutation: an earlier form ran it after filament()->extrude(dE) and so
extruded 2*dE on the fallback path.

Known limits of that fallback, since it is worth stating rather than discovering:
emitted relative E is conserved only to per-segment rounding (a radius-5
semicircle with dE=1.5 emits 1.50012 across 36 segments); the 0.005mm bound is a
logical-frame bound, about 0.00855mm in machine space under a 45-degree belt
shear; unequal endpoint radii and non-finite inputs are unchecked. Ordinary
export takes the original polyline when the mapping rejects arcs, so this path
is a fallback rather than the normal route.

Known gap, not claimed fixed: classic wipe towers have their own
enable_arc_fitting and their own G2/G3 emitter in GCode/WipeTower.cpp, which
should_disable_arc_fitting() does not govern. Belt printers are barred from
classic wipe towers; a remapped Cartesian printer is not.

Tests in tests/fff_print/test_gcodewriter.cpp: reverse-X remap with unknown and
with known position plus an identity control; eager_lift emitting nothing and
recording nothing; the arc-capability matrix including the Z-only cases; and the
tessellated fallback. E accounting is asserted through used_filament() rather
than E(), which resets per line in relative-E mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:58:19 -05:00
harrierpigeonandClaude Opus 5 e695da66df GCodeWriter: extract MachineKinematics, delete BeltGCodeWriter
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
2026-09-08 23:57:55 -05:00
HanifKohandraistlin7447 4deadc9dce Make Tree-Support Deterministic (#15565)
* Make tree support deterministic without giving up its parallelism

* Break equal-distance ties in the tree support MST by coordinates

* test: cover the determinism this PR fixes

The MST unit tests here cover the tie-break, but the drop_nodes rework
has no test.

Adds two cases to the tree support suite. The thread-scheduling one
slices five configs twice each and compares the support point sequence,
which is what the node ordering moves. The MST tie one pins the branch
diameter and line width that carry Prim's equal-distance ties into the
toolpaths.

slice_with_tree_support takes an optional config list so the second case
can add the tree parameters it needs, and the double-slice comparison is
shared rather than written twice.

Both fail on main without this PR. The first passes from 60d1ceb580, the
second from e148865dd6.

---------

Co-authored-by: raistlin7447 <kris.austin@gmail.com>
2026-09-09 12:33:42 +08:00
Lam Wei Lun a4c399d250 Merge main and resolved conflicts 2026-09-09 11:05:52 +08:00
Kris Austin 0f5891f25d build: clear 107 warnings - dead private fields (#15574) 2026-09-08 18:36:48 -03:00
SoftFever 994d2de5d6 Merge branch 'main' into pr/tommasobbianchi/15238 2026-09-08 21:54:22 +08:00
HanifKoh 58bf267fdd Scale Min Junction Width to Prevent Fuzzy Skin From Failing Slice (#15566)
* Fix fuzzy skin failing the slice: the minimum junction width was unscaled

* Unit Tests For Fuzzy Fix

* Cover ridged multifractal noise in the fuzzy skin width floor test

Its output is not bounded to [-1, 1], so it scales past the configured
thickness and drives the junction width negative. The floor has to hold
for any noise value, not just an in-range one.
2026-09-08 16:57:33 +08:00
HanifKoh 8e064659ad Fix Non-Deterministic Slicing - Order Per-Layer Intersection Lines Canonically (#15563)
Fix nondeterministic slicing: order per-layer intersection lines canonically

Facet processing in slice_make_lines() is parallel, so the per-layer line
order depended on thread scheduling. make_loops() consumes that order for
island order and loop start vertices, so the same model could slice to
different G-code run to run.

Sort each layer's lines by a topology-based key. edge_type and flags are
appended to the key purely to break ties: two lines can share every id and
endpoint (a Horizontal facet can emit such a pair) and std::sort is not
stable, so without them that pair's order would stay thread-dependent.
2026-09-08 16:33:12 +08:00
SoftFever 12d43433dc Merge branch 'main' into pr/tommasobbianchi/15238 2026-09-08 14:28:31 +08:00
Maximilian Ghazanfar bcb4f17d9a fix(cli): resolve inherited presets through vendor manifests (#15438)
* fix(cli): resolve inherited presets through vendor manifests

* fix(cli): resolve typeless inherited presets

Probe the configured preset collections when a preset JSON omits its type. Reject missing, cross-type, and duplicate identities instead of silently selecting a candidate.

* fix(cli): allow missing app config during preset resolution

* fix(cli): tolerate malformed app config during preset resolution
2026-09-08 12:12:55 +08:00
SoftFever 37e1582c4c redesign filament_id (#15513)
# Description

This PR redesigns `filament_id` across OrcaSlicer's profile library, so
that one filament
product now carries one consistent id everywhere it ships, instead of a
hand-written value that
unrelated materials routinely shared. Minting scripts and CI checks come
with it so future
profiles comply by construction: a new filament takes its id from the
tool, and the checks
reject a hand-written, duplicated or drifted one before it can merge.

Unique, non-duplicated ids are a precondition for AMS-style spool
syncing to be dependable —
the id is what a printer matches a physical spool against, and while two
products share one, the
match is a coin toss. This PR lays that groundwork. A follow-up PR will
publish an OrcaSlicer
materials reference on the wiki, giving every system profile one place
to point at.

`filament_id` names one filament product, and it is what a device
matches a physical spool
against: Bambu AMS, Creality CFS, the Qidi box, Klipper and Snapmaker
all resolve a tray to a
preset by id alone, first hit wins. Those ids were written by hand, and
on `main` 99 of them
stand for 674 different products — `GFL99` alone covers 132, from
Anycubic PLA to Bambu PLA
Matte. Every consequence is silent: a spool resolves to whichever preset
happens to load first,
tray names and support-material flags are read off the wrong material,
and the second preset
holding a duplicated id disappears from the tray-edit dialog entirely.

The id is now a hash of `(filament_vendor, filament_type, filament
name)`, so one spool product
carries one id in every bundle that ships it, two vendors shipping the
same product converge on it
without coordinating, and a collision cannot be authored by hand. Every
ambiguity across 48
vendors is fixed rather than excused — no grandfather list and no
per-vendor carve-out, Bambu's
bundle included — and the resulting landscape is frozen in
`scripts/filament_id_snapshot.json`,
so a change to any filament's identity lands as a reviewable diff to one
file. CI now runs the
duplicate-subtype validation tree-wide instead of over Bambu only.

Letting the id follow the product meant correcting the identities
themselves. Generics that
shipped under a vendor prefix now have one name and one id everywhere
(`Blocks Generic PETG` is
`Generic PETG @Blocks`), presets whose `filament_vendor` or
`filament_type` contradicted the
spool are fixed, and duplicate pairs are collapsed onto the
better-configured survivor. Renames
carry `renamed_from`, so existing projects and user presets keep
resolving.

Bambu's printers, its AMS and its cloud know only Bambu's own catalog
ids, so those ids leave
the profiles entirely. The Bambu bundle mints like every other vendor,
and the printer agent
swaps in the catalog value only where an id crosses to or from a Bambu
printer — outbound MQTT
and FTP, the AMS mapping sent with a job, the ids written into a 3mf the
printer will read —
mapping back on the way in, so status messages, SD-card prints and
projects saved by an older
Orca or by BambuStudio all still resolve. The correspondence is
generated from BambuStudio's
own shipped bundle by `scripts/update_bambu_filament_ids.py`; an id with
no row is forwarded
untouched, a missing or malformed map degrades to no translation rather
than taking the app
down, and an agent whose printers already speak Orca's ids translates
nothing.

Two device-side bugs this work surfaced are fixed here as well. The Orca
Filament Library was
missing from the AMS material and calibration dialogs, which treated a
filament with no
`compatible_printers` as compatible with nothing while the rest of the
app treats it as
compatible with everything; and Creality CFS sync on a K2-family stock
0.4 nozzle picked the
wrong preset, an imprecise matcher that duplicate ids had been masking.

`docs/HLSD/filament_id.md` is the authoring rule for all of this.
`scripts/orca_id_tool.py`
mints both `filament_id` and `setting_id`, replacing
`assign_vendor_setting_ids.py`, and the
local `check_profile` scripts gained per-vendor scoping so one vendor
can be checked without a
tree-wide run. `scripts/tests/` covers the tooling; `tests/slic3rutils/`
covers the boundary
translation and the CFS matcher.

Ids change for most products and nothing forwards the old value, so a
tray or a calibration
record still holding one falls back to matching by filament type until
the filament is selected
once. Beyond the profile fixes above no print settings change, except
that a few presets stop
claiming printers a dedicated variant already covers.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

`scripts/check_profile.sh`, the local twin of the "Check profiles" CI
job, with
`scripts/check_profile.bat` as its Windows entry point, passes on this
branch: the extra JSON
check reports no errors and no warnings, system validation and the now
tree-wide
filament-subtype check load all 66 vendors cleanly, all 1013 printer
presets slice, and
custom-preset validation passes against every fixture archive from
v1.9.0 to v2.4.2.

The id tooling has 176 unit tests (`python -m unittest discover -s
scripts/tests`). The C++
suites pass too — 332 in `tests/libslic3r` and 126 in
`tests/slic3rutils`, the latter including
the boundary-translation and Creality CFS matching cases added here.

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-09-07 19:22:11 +08:00
HanifKoh 886d43d37a Fix Support Fill Cost Thresholds Being Frozen By The First Call (#15564)
Fix support fill cost thresholds being frozen by the first call
2026-09-07 16:35:36 +08:00
SoftFever 4b104bb574 Merge branch 'main' into feature/filament_id 2026-09-07 11:53:50 +08:00
SoftFever 3500a1e588 unify id generation scripts 2026-09-07 10:46:59 +08:00
Kris Austin 43ce8c5e46 fix: SLIC3R_PCH=OFF now builds on Windows, allowing compiler caching (#15552) 2026-09-06 18:06:18 -03:00
Rodrigo FaselliandIan Bassi 85dc866425 Spiral Inset infill (spiral-concentric infill) (#15085)
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-09-06 11:36:23 -03:00
SoftFever 06e665fca7 Merge branch 'main' into feature/filament_id 2026-09-06 22:34:06 +08:00
Kris Austin 0b6bbd85bd build: clear 75 warnings - hidden base overloads (#15542) 2026-09-06 11:08:57 -03:00
ocidburn 6014f3fb43 Fix: guard the fifth null deref of the same kind, on the project-settings path (#15451) 2026-09-05 17:05:11 -03:00
Ian Bassi cd011e6385 Fix CHB parciall bridge (#15387) 2026-09-05 15:22:05 -03:00
Kris Austin 2ad9c87dda fix: GIT_COMMIT_HASH forces full rebuilds and defeats compiler caching (#15537) 2026-09-05 14:08:38 -03:00
Kiss Lorand 0224741105 Fix organic support being printed into object top surfaces (#15539) 2026-09-05 11:48:35 -03:00
Kiss Lorand 134b9ad96e Fix organic tree support generating one less bottom interface layer than defined (#15525) 2026-09-05 11:47:58 -03:00
Clifford GarwoodandClaude Opus 5 6c67fcfe07 Read IMEX geometry defaults from print_config_def instead of literals
Five sites carried a hardcoded fallback for imex_tools_per_gantry that had
to match the value registered in print_config_def, with nothing enforcing
the agreement, and several explanatory comments miscounted the sites they
described or cited stale line numbers.

Add imex_cfg_int/_float/_bool/_enum<T> to IMEXHelpers, which return the
value registered for the key when it is absent from the config, so the
registration is the single source and there is nothing left to keep in
sync. Route every read of the IMEX geometry keys through them: 32 call
sites across IMEXZones, PartPlate, GCodeViewer and Tab. The only direct
lookup left is the bail in PartPlate::imex_multicolor_block_reason, which
must not default because it reports a routing conflict and a defaulted
grid would produce a false warning.

imex_cfg_enum uses dynamic_cast on both halves rather than the type()
comparison the others use: every ConfigOptionEnum<T> reports coEnum, so a
type() check cannot tell one enum type from another and would cast a
ConfigOptionEnum<OtherEnum> to the requested T. The ConfigOptionPercent :
ConfigOptionFloat inheritance that rules dynamic_cast out for the float
accessor has no analogue for enums.

Correct the comments that prompted this: the cache-key input list in
PartPlate named five inputs for a nine-part key, the ImexMarkerKey note
in GCodeViewer called imex_tool_layout an input only the preview reads
when the plate keys it too, and four file:line citations pointed at the
wrong lines. Values are unchanged at every converted site; cache key
strings keep their existing representation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 20:34:04 -04:00
HanifKoh 1170b048e8 [CLI]: Fix Plate Config Reading and BuildVolume Height Checks (#15479)
* 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
2026-09-04 23:24:59 +08:00
HanifKoh df30e22427 [CLI]: CLI Crash Guards (#15477)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

Part 1 of 3 of the CLI-mode bug sweep, split out of #15452 per review
feedback there. This PR contains the crash fixes.

## Fixes

- **`--outputdir`/`--datadir` with a missing parent directory aborted**
via unguarded `create_directory`. Directories are now created
recursively, with a graceful early exit and a specific error message if
creation fails.
- **`--slice` + `--export-3mf` segfaulted on a from-scratch slice**:
`ConfigOptionVector::get_at()` on an empty vector is `.front()` of an
empty vector (UB). Guards added for `filament_color`/`filament_id` at
the CLI call site, and inside `DynamicPrintConfig::get_filament_type`
for `filament_type`/`filament_is_support`/`filament_id`. Only *empty*
vectors are treated as missing — the existing clamp-to-front behavior
for merely out-of-range indices is preserved, so GUI callers are
unaffected.
- **OOB heap write from stale `filament_self_index` on
`--load-filaments`** (fixes #14181): a 3MF carrying more
`filament_self_index` entries than loaded filaments wrote past the end
of `old_variant_counts`. The guard validates both bounds — entries `>
filament_count` *and* non-positive entries (`< 1`), since a single `0`
in an otherwise-valid array indexes `old_variant_counts[-1]`.
- **Wrong printable-area check** for non-rectangular beds: use the
printable area's bounding box instead of a naive vertex calculation
(fixes #15363).
- **`nozzle_height` and `align_center` were not read into the arrange
config** in CLI mode.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
- Repro'd each crash on CLI before the fix; all resolved after.
- `tests/libslic3r` suite passes; full binary builds clean on Linux.
- Added `get_filament_type` unit tests 

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-09-04 15:46:03 +08:00
Lam Wei Lun c6ab725584 Fixes issue with mixed filament being loaded into a real slot 2026-09-04 14:41:20 +08:00
Lam Wei Lun bbb4681b32 Merge branch 'main' into publish_3mf 2026-09-04 12:24:57 +08:00
Clifford GarwoodandClaude Opus 5 c09ce3a0d1 Merge upstream main: CLI argument parsing, GUI string fixes, nozzle type undo tracking, warning cleanups
No IMEX code upstream, so nothing in this merge touches the feature. All 21 overlapping
files auto-resolved; verified every upstream addition is present in the merged tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 00:17:36 -04:00
HanifKoh 57ce18d70d [CLI]: CLI Argument Parsing Fixes (#15478)
* Reject invalid CLI argument values instead of silently accepting them

* Add read_cli accept/reject tests

* Update Option Type for LogFile argument

* Add read_cli vector option tests

* Accept common bool spellings on the CLI, cover --logfile in tests

* Add unit tests for truthy bool parsing
2026-09-04 11:31:42 +08:00
TheLegendTubaGuyandRodrigo Faselli 7acea3ed09 Honor symbolic default bed types for new printers (#15273)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-03 19:42:31 -03:00
Kris Austin b370d8ef31 build: clear 31 warnings - copy and move declarations (#15507) 2026-09-03 18:35:10 -03:00
Clifford GarwoodandClaude Opus 5 5eac300d91 Use IDEX/IQEX in the user-facing strings
Closes review comment 10.

`461c69c83e` settled this in April — IMEX internally, IDEX/IQEX as the user-facing label — but the
UI strings were never converted. Every translated string naming the feature now reads IDEX/IQEX:
41 occurrences across the printer and process option labels and tooltips, the modes editor, the
plate mode indicator, the pre-slice warnings, the placement refusals and the slicing errors. The
reviewer listed eight; the rest were in the same class.

Nothing else moves. The config keys keep the `imex_` spelling — `is_imex`, `imex_mode_names`,
`imex_parallel_mode` and the rest are on-disk format in existing printer presets and 3MF projects,
so renaming them would break every profile and project already saved. C++ identifiers, filenames,
comments and test names keep IMEX as well: it stays the internal name of the subsystem, which is
what covers the topology space (one gantry with 2-4 tools, 2x1 and 2x2 grids) that neither acronym
names on its own. Where a tooltip quotes a key, the key spelling is preserved and only the feature
word around it changed.

The `is_imex` tooltip is reworded rather than substituted: it already named the hardware families
parenthetically, so a literal replacement would have said IDEX/IQEX twice in one sentence.

No translation impact — no IMEX string had reached OrcaSlicer.pot or any catalogue, so there is
nothing to migrate. One test asserted on the old error text and now matches the new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 10:05:37 -04:00
SoftFever 7c9b38ba04 remove retired_filament_ids.json 2026-09-03 21:03:42 +08:00
Ian Chua 07dafaf299 fix: default enable-ota flag and fixing startup missing vendor 2026-09-03 17:13:09 +08:00
Ian Chua c5952c3308 Merge branch 'main' into fix/opc-support-for-ota 2026-09-03 14:54:51 +08:00
ExPikaPaka 4a48fc770d Add alternative backe pipeline 2026-09-03 08:48:03 +02:00
Lam Wei Lun 904796cf24 Correctness fixes. Remove hard-coded appends for printer settings 2026-09-03 14:12:51 +08:00