When load_presets() drops a preset because its parent does not exist, the
only trace is a log line. The CLI then resolves --load-settings /
--load-filaments against the loaded bundle and reports "Preset was not found
in the loaded bundle", which points at the resolver rather than at the real
cause.
Record the presets dropped for a missing parent in the collection, keyed by
file, and have resolve_preset_config() report that parent by name when the
source file is one of them.
A preset that inherits another preset from the same directory was dropped at
load time. load_presets() resolves "inherits" with find_preset2(), which
searches m_presets, but the presets it loads are staged in a local vector and
merged into m_presets only after the loop - so no preset could be the parent of
another preset loaded by the same pass. Only a parent loaded earlier (a system
preset, or one in the "base" subdirectory) resolved.
The drop was silent in the GUI. Since the CLI resolves --load-settings /
--load-filaments by matching the source file against the presets in the loaded
bundle, it turns into a hard failure there: a user preset inheriting another
user preset exits -5 with "Preset was not found in the loaded bundle".
Load in passes instead: a preset whose parent is not in the collection yet is
deferred and retried after the pass merges what it loaded, so each pass resolves
one more level of the hierarchy. A pass that resolves nothing reports the
remaining parents as missing, which also terminates an inheritance cycle. The
work list is sorted so the outcome does not depend on directory iteration order.
# Description
The pre-slice wipe tower size estimate existed twice:
`Print::wipe_tower_data()` (validation) and
`PartPlate::estimate_wipe_tower_size()` (GUI placement clamp, default
placement, preview, arrange, CLI placement) — with a third partial copy
in the CLI, which resolved the brim itself around the second. They were
hand-written twins reading their inputs from different places, so
validation could measure a tower with one number after the clamp had
placed it with another.
This PR extracts the estimate into one function,
`estimate_wipe_tower_footprint()` in
`src/libslic3r/GCode/WipeTowerEstimate.{hpp,cpp}`. It takes a
`ConfigBase&` (static `PrintConfig` and GUI/CLI `DynamicPrintConfig`
both work), the filament count, layer height and tallest object height,
and returns width, depth, height and the resolved brim width.
`Print::wipe_tower_data()` and a new
`PartPlate::estimate_wipe_tower_footprint()` become thin adapters around
it; `PartPlate::estimate_wipe_tower_size()` had no callers left and is
deleted.
**Inputs made to agree** — sharing the arithmetic is not enough when
each caller derives the inputs from its own view of the model:
* **Layer height** — thinnest layer among the objects on the plate,
resolved per object (Print used the first object's, PartPlate the
preset's).
* **Objects setting the height** — `PartPlate::get_extruders` counts an
object if *any* instance is on the plate, matching `PrintApply` (it only
looked at instance 0).
* **Height per object** — per on-plate instance, from the cached convex
hull (same z extent as the mesh). `PrintObject::size()` still measures
the model's first instance, so objects whose instances differ in scale
or x/y tilt can still disagree; that is inherent to the two data
sources.
* **Wipe tower filament** — counted for every caller, since
`Print::extruders()` adds it to the tool ordering even when unused.
* **Rib width cap** — kept for both (Print lacked it).
* **Config source** — everything read from the config passed in
(PartPlate read `m_print->config()`, stale on fresh plates and in the
CLI).
* **Dual-nozzle test** — `nozzle_diameter.size()` from the config for
both.
**One decision about whether a tower exists.** The rectangle branch
sized a tower the generator never builds while the rib branch reported
none for one it does; with rib as the shipped default, a single-filament
plate that still prints a tower (custom G-code tool changes) validated
against depth 0, collapsing the collision/exclusion hull to a point. The
purge volume is computed first, and an empty footprint is returned only
when nothing is purged, there is no tool change, and nothing else puts a
tower on the plate. The reason a single config cannot see arrives as a
resolved input: validation counts `Print::extruders(true)`.
A raft is deliberately **not** one of those reasons.
`DynamicPrintConfig::normalize_fdm_2` clears `enable_prime_tower` for a
plate that purges one filament unless smooth timelapse or wrapping
detection is on, and `Print::apply()` runs it, so a raft alone leaves no
tower to reserve for. (It also keeps the tower for a single *mixed*
filament, which this does not model — `Print::extruders(true)` does not
expand mixed filaments.)
**Two implementation notes:**
* Enums are read **by value**: a preset-built `DynamicPrintConfig` holds
`ConfigOptionEnumGeneric`, so a `dynamic_cast` to `ConfigOptionEnum<T>`
is null for exactly the config the GUI and CLI pass. The tests build
their configs the way `PresetBundle::full_config()` does, so that
storage is what gets tested.
* `PartPlate::estimate_wipe_tower_footprint()` is CLI-reachable, so
`get_extruders(bool)` gained a config-taking core with the identical
body; the GUI wrapper passes the app's presets, the adapter passes the
config it is given. `get_extruders_under_cli()` was not substituted: it
filters the plate's instances differently (skips unprintable ones, keeps
ones the plate flags as outside), so the GUI's filament set would have
changed in edge cases.
**Also fixed here:** `WipeTowerData` carries the effective width — set
by the estimate, and then by both planners at generation, so it never
disagrees with its neighbour `depth`; the preview and the containment
check take *whether there is a tower at all* from the footprint instead
of each re-deriving it; the config-taking `get_extruders` answers for an
object-less (`.gcode.3mf`) plate the way the wx overload does; the
preview takes body *and* brim from the plate's own footprint (an auto
brim drew every plate with the selected plate's brim);
`estimate_wipe_tower_polygon` builds its margin from the resolved brim
("Auto" gave a margin of 0) and no longer calls `std::clamp` with `hi <
lo`; the estimate falls back to declared defaults instead of hand-copied
constants. `estimate_wipe_tower_size()` /
`estimate_wipe_tower_polygon()` lose four parameters every caller took
from the same config.
## Behaviour changes reviewers should know about
G-code is never affected; no 3MF, profile or string changes. But this is
**not** a pure refactor:
1. **Validation now reserves what the placement clamp reserves**, which
is in places larger than before. A saved 3MF with a tower close to an
exclusion area or the rear edge can be rejected where it previously
sliced; dragging resolves it since the clamp agrees. Nothing re-clamps a
stored position on load (out of scope; the CLI side lands with #15518).
2. **`PartPlate::get_extruders` counts any-instance-on-plate**, which
reaches every caller of it, not only the estimate. It is `PrintApply`'s
rule and closes a GUI/CLI divergence.
3. **`estimate_wipe_tower_polygon`'s rear/right bound is looser by one
brim width** (it subtracted the brim twice).
4. **A single-filament plate with a rib wall no longer reserves a
phantom tower.**
5. **A single-filament plate whose tower comes from wrapping detection
is now validated against the bed.** Neither the old estimate (which read
only the wall type and smooth timelapse) nor the old containment gate
(the filament count or smooth timelapse) knew about that tower, so
between them it was never checked. It is printed, so it can be rejected
now.
Not addressed: the estimate still does not read `wipe_tower_type` or
per-filament `filament_prime_volume`, inherited unchanged from both
copies (the generated Type 1 tower is ~10 mm larger than the estimate on
Bambu profiles). #15516 mirrors the planners and folds into this
function on rebase.
## Verification
Before/after on the same fixtures with a main build and this branch, all
numbers read from the CLI (details, method and the real-tower and
arrange-clamp tables in the first comment):
| Fixture (divergence) | Side | Before (w × d, mm) | After (w × d, mm) |
|---|---|---|---|
| control | GUI/CLI · validation | 23.585 × 23.585 · 23.585 × 23.585 |
same |
| per-object layer 0.1 | GUI/CLI · validation | **23.585** · 31.637 |
**31.638** · 31.637 |
| unused `wipe_tower_filament` | GUI/CLI · validation depth | **39.332**
· 44.542 | **44.541** · 44.542 |
| tall object, instance 0 on another plate | GUI/CLI · validation |
**23.585** · 29.391 | **29.390** · 29.391 |
| rib cap binds | GUI/CLI · validation | 11.170 · **13.910** | 11.170 ·
**11.170** |
Before, the two estimates disagree on every divergence fixture; after,
they agree to the 0.001 mm bisection resolution, the control is
unchanged, and G-code is byte-identical. GUI screenshots of the preview
on both binaries are in the same comment.
The table was measured on the first commit; none of its fixtures uses a
raft or a zero purge volume, so the second commit does not move them.
G-code equivalence was re-checked on the final tip: `Cube.3mf` sliced by
a `main` build and by this branch is byte-identical.
# Screenshots/Recordings/Graphs
Before:
No Wipe Tower Preview:
<img width="2068" height="871" alt="image"
src="https://github.com/user-attachments/assets/7875a944-b8db-4bbc-b380-e8188a45caa7"
/>
After:
Has Wipe Tower Preview:
<img width="2551" height="882" alt="image"
src="https://github.com/user-attachments/assets/b4413695-4f24-4aa3-bae4-57304e8b7865"
/>
**Per-object layer height reaching the preview.** One object with a 0.1
mm override against a 0.2 mm preset. `main` sizes the previewed tower
from the preset, so it is smaller than the one validation reserves and
the one that prints; this PR sizes it from the object. Captured
headlessly on both builds from the same project, top view:
<img width="1408" height="596" alt="D_per_object_layer_height"
src="https://github.com/user-attachments/assets/989920fc-9f14-4658-8a3d-681c92a7f754"
/>
Measured over the four evidence fixtures on both builds, this is the
only one of the corrected inputs that changes what is drawn: the others
(an object contributing through a non-zero instance, an unused
`wipe_tower_filament`) change the estimate by amounts confirmed through
the CLI bisection above, but leave the rendered tower pixel-identical.
Arrange is unaffected either way — the tower enters the arranger as a
fixed obstacle (`m_unselected`), so it never moves.
## Tests
* `tests/libslic3r/test_wipe_tower_estimate.cpp` (10 cases / 104
assertions): rectangle and rib sizing, stability floor and auto brim,
single-filament cases (timelapse, wrapping, and a raft *not* reserving
one), a tool change reserving the floor when the purge volumes resolve
to zero, both wall types agreeing on tower existence, dual-nozzle
volume, the shipped flush-matrix path, default fallback for a missing
key, and a {rectangle, cone, rib} × {type1, type2} matrix asserting a
preset-shaped `DynamicPrintConfig` and a static `FullPrintConfig` give
the same footprint.
* `tests/fff_print/test_wipe_tower.cpp`: what `Print` feeds the
estimator — thinnest object layer height, effective width reaching
validation, the width staying current through generation, a
single-filament plate reserving a tower only when one is really printed
(raft no, smooth timelapse yes), and a wrapping-detection tower being
bed-validated. The last two fail on `main` and on the first commit of
this PR.
* Full suites green on this branch: `libslic3r_tests` 342 cases / 58325
assertions, `fff_print_tests` 174 cases / 3152 assertions. `--target
all` builds clean (including `OrcaSlicer_profile_validator`, which needs
`-DORCA_TOOLS=ON`). No new warnings.
* CLI evidence run above; its unused-`wipe_tower_filament` fixture is
also the regression check for the adapter under the CLI, which no unit
test can reach (`PartPlate` needs a GL context).
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
A plain CLI slice ran none of the placement sites, so a stored or
default tower position that no longer fits the tower the plate needs
went straight to the generation-time error. The slice loop now applies
the same clamp the GUI applies on reload to every plate it is about to
slice, skipping only plates that print no tower: by-object plates with
more than one instance, and plates whose footprint estimate is empty
(which covers single-filament plates without smooth timelapse, wrapping
detection or a raft). The plate's filaments come from the same
config-driven derivation the estimate uses everywhere else.
The two arrange sites read the brim width from the right option when
padding the default position; an auto brim uses its 8 mm cap there,
since the object heights are unknown before the estimate runs.
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.
The validator forces a two-filament print with the prime tower on and
slices it at the config default position (x 15, y 220), which lies off
any bed shallower than the tower. It calls validate() but slices
regardless, so the off-plate tower was exported silently; with the
generation-time footprint check it is rejected instead, and 522 of the
1013 printer presets failed the slice check.
The validator now positions the tower the way the GUI and CLI do before
slicing: beside the centred cube, clear of the edge exclusion strips some
beds carry, then pulled inside the printable outline by the tower's own
estimated footprint.
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.
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.
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.
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.
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.
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.
* 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>
* 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.
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.
The bed was declared as 0x0, 20x0, 200x200, 0x200 - a triangle - where the
0.4 nozzle profile and the printer have the 200 x 200 square. Found by the
profile validator once it placed the prime tower beside the test cube:
no tower fits inside that outline.
# 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)
filament_id is the plain mint of the product triple (filament_vendor,
filament_type, filament name), and nothing else feeds it. The tooling used to
accept any salt iteration of a preset's own triple, and its minting policy
stepped past ids that other products held in the tree or in the snapshot, so
which id a product got could depend on history. Every "salt split" in the tree
masked a redundant preset rather than a real need, and no shipped id is
salted, so salting goes entirely: no salt parameter, no id policy object, and
--generate no longer reads the snapshot.
--check now holds every declared and every inherited id to that one value and
lists each preset that misses it, variants under a wrong root included, instead
of folding them into the root's error. Two products whose triples mint one id
is reported as a collision naming both, and --generate refuses to write it;
the remedy is a rename so the triples differ. The Bambu catalog map generator
keys its rows by the same mint rather than by what the tree already ships.
No id changes: all 913 ids in the tree are already the mint of their triple,
so --generate is a no-op and the snapshot is untouched.
"Snapmaker PLA-CF @U1" and "Snapmaker PLA-CF @U1 0.4 nozzle" are the same
product on the same printer: both pin "Snapmaker U1 (0.4 nozzle)", and only a
filament_id salt split kept them apart. The first came with the original U1
profiles (#10225); the second with the tool-changer rework (#15039), which
added the 0.4/0.6/0.8 nozzle variants and left the older preset behind.
Drop the older one and re-mint the 0.4 variant onto OFhQf8ou, the id its 0.6
and 0.8 siblings already carry, so the product holds one id across the three
nozzles; renamed_from redirects the retired name. Users of the retired preset
get the #15039 tuning - 39 keys differ, including nozzle 230 -> 240 C, plate
55 -> 65 C and vitrification 150 -> 45 C - which is what the 0.6 and 0.8
variants already ship.
"Snapmaker PLA-CF @U1 base" stays. #15039 orphaned it by having the nozzle
variants inherit fdm_filament_pla directly, so it is unreferenced, but it is a
base in the vendor's own convention - 10 of the 14 @U1 families with nozzle
variants wire them to one - and it holds U1 loading and cooling values the
variants never set. Wiring them to it would change slicing output, which is
Snapmaker's call to make, not this PR's.
Claude-Session: https://claude.ai/code/session_01Q3zm9HuyskkSb4hynviV99
"Update re:3D profiles" (#13750) added the tuned "@0.4/@0.8 nozzle" and
"@0.8/@1.75 nozzle" variants but kept the five original un-suffixed presets,
trimming each to a stub that overrides only filament_vendor while still
claiming every printer of both nozzles. On any re3D printer the stub was
selectable alongside the ~50-key variant that actually tunes the material,
which is what forced five filament_id salt splits to keep the pair apart.
Drop the stubs. The variants already carry the products' ids, so nothing is
re-minted; renamed_from redirects each retired name to the smaller-nozzle
variant, and "re3D rPETG @0.8 nozzle" additionally takes over the
"re3D Greengate rPETG" mapping that lived on the deleted umbrella. Each
printer model's default_materials now lists the variants for its own
nozzles rather than the stub.
Cubicon's nine "@base" presets were the only instantiated ones of the 196
"@base" filament presets in the library; the other 187 are instantiation:false.
Being selectable, and claiming xCeler-I and xCeler-Plus, they put two presets of
one product on those two printers - which is what forced the nine filament_id
salt splits: one product deliberately kept on two ids so AMS matching stayed
unambiguous.
Flip them to instantiation:false and drop the compatible_printers, setting_id
and filament_settings_id that a base has no use for, matching what the other
150 bases carry. The three "@Cubicon xCeler-{I,Mini,Plus} 0.4 nozzle" variants
keep one printer each, so every printer still sees exactly one preset per
product and they converge on its single id; the nine salted ids retire.
A base is not added to the preset collection, so user presets that inherited
the selectable "@base" - the v2.3.2-v2.4.2 fixtures have one per material -
would lose their parent. renamed_from on the xCeler-I variant, the first model
the base ever claimed, redirects them; a preset built on "@base" while printing
on an xCeler-Plus narrows to the xCeler-I.
The six default_materials / default_filament_profile fields named
"Cubicon PLA @Cubicon xCeler-<model>", dropping the " 0.4 nozzle" the presets
actually carry, so none of them ever resolved; each model now names its own
variant.
Claude-Session: https://claude.ai/code/session_01Q3zm9HuyskkSb4hynviV99
On macOS wxWidgets reports Ctrl+left as a synthetic right button, which is
what made Ctrl+drag pan the canvas. #14999 added an unconditional correction
of the event's button state from wxGetMouseState(), which reports the
physical buttons and knows nothing about that synthesis, so the synthetic
right button was overwritten with a plain left button on every event.
Ctrl+drag then matched the left button mapping and rotated instead of
panning.
Apply the correction only when the event carries no button state at all.
On macOS wx populates button state only for the mouse-down and mouse-dragged
event types, which are also the only ones the Ctrl+left translation touches,
so the ImGui capture fix keeps every event it was added for.
Fixes#15214
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
A filament_id is now exactly what the preset's own filament_vendor,
filament_type and filament name mint, wherever it inherits from. Inheriting
settings no longer limits what a preset may claim, so the checks that policed
inheritance are gone, and so are the four grandfather lists that held thousands
of presets as permanent exceptions. The snapshot records sanctioned state rather
than excuses: one entry per id, carrying the product it names beside the presets
claiming it.
Profiles that disagreed are corrected instead of excused. The Elegoo TPU and
PAHT roots were named for a different product than all of their variants and
become TPU 95A and PAHT-CF; Elegoo PET-CF gains the filament_type its variants
already set; the Snapmaker breakaway support presets get an id of their own
rather than riding the PVA chain; and a BBL preset name carrying a doubled space
is fixed behind renamed_from. Their ids re-mint from the corrected identities.
The tooling and the design note also drop the word "family", which invited
reading a brand's Lite and Pro spools as one id.
No change to slicing output — only filament_id values, three preset names, the
inherits lines following those renames and the vendor indexes move.
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.
# Description
Under `core.autocrlf=input`, a Windows dependency build fails while
building CPython:
```
The system cannot find the batch label specified - begin_search
Cannot locate python.exe on PATH or as PYTHON variable
error MSB8066: ... exited with code 3
```
`git apply` inherits the caller's git configuration, so it rewrites the
patched `PCbuild/find_python.bat` to LF, and `cmd.exe` cannot resolve
`goto` labels in an LF batch file. `git init` already runs in the
extracted source, so setting `core.autocrlf` on that repository fixes it
and leaves the shared `PATCH_CMD` alone.
Only python3 is affected, its patch being the only one under `deps/`
that touches a `.bat`. `core.autocrlf=true` (the Git for Windows
default) and `false`/unset were never affected.
If a dependency build later fails with `patch does not apply`, delete
`deps/build` and rebuild. `git apply` is not idempotent, and that is
independent of this change.
## Tests
Visual Studio 2026 (18.6.3), `core.autocrlf=input`, built from an empty
`deps/build-dbg`:
| `deps debug` on | `find_python.bat` | CPython |
|---|---|---|
| `upstream/main` | 0 CRLF / 95 LF | fails at `begin_search` |
| this branch | 94 CRLF / 1 LF | builds |
It then stops at the debug staging step, a separate bug fixed by #15353;
with both applied it installs `libpython`. I hit this on a debug build,
but the patch step has no Debug/Release conditional.
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)