mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-01 06:16:58 +00:00
398 lines
28 KiB
Markdown
398 lines
28 KiB
Markdown
# filament_id: generation rule + system-profile fix plan
|
||
|
||
Follow-up to PR #14459 (commit `c2e91cb8`, validator `-f` / `check_duplicate_filament_subtypes`).
|
||
Goal 1: a filament_id generation rule for all vendors. Goal 2: an error-pattern taxonomy and a
|
||
migration plan that removes every ambiguous filament_id, without touching Bambu (BBL) profiles.
|
||
|
||
All numbers below are reproducible: a loader-faithful audit script re-derives the validator's
|
||
output **exactly** (1256/1256 printer-level errors, 356 logical collision groups, 30 vendors).
|
||
Every code claim was verified against source with `file:line` references.
|
||
|
||
---
|
||
|
||
## 0. Executive summary
|
||
|
||
- `filament_id` is a **material-family id**: one id per commercial product line, shared by all
|
||
of that material's per-printer/per-nozzle variants. Matching is always `(filament_id +
|
||
printer compatibility)`; the invariant from PR #14459 is *per printer preset, at most one
|
||
compatible instantiated filament preset per id*.
|
||
- Every modern device ecosystem funnels through this id — not just Bambu AMS: Qidi box,
|
||
Creality CFS, Klipper AFC/Happy Hare, Snapmaker all emit/consume `tray_info_idx`
|
||
(see §1). Several consumers match **globally, without printer scoping**, so two *different
|
||
materials* sharing one id is unsafe even across vendors.
|
||
- **Proposed rule (§3):** deterministic, script-minted ids — `OF` + 6 base62 chars from
|
||
`uuid5(vendor + family)`, declared **only on family-root (`@base`) presets**; variants inherit.
|
||
Nobody ever invents an id by hand; CI prints the expected id when one is missing. Existing
|
||
unambiguous ids are grandfathered and frozen; `GF*` (Bambu), `QD_*` (Qidi device protocol),
|
||
and `P<7-hex>` (user custom presets) are reserved namespaces that must never be minted into.
|
||
This was selected by an adversarial design/judge process over a registry-grammar alternative
|
||
and a structure-only alternative, then stress-tested; amendments from that stress test are
|
||
folded in below.
|
||
- **Migration (§5):** fresh-never-reused ids only, family-atomic, names never changed. This is
|
||
provably safe: user presets re-derive `filament_id` from their parent on every load
|
||
(`Preset.cpp:1658-1682`), 3mf resolves presets by name+config (`Preset.cpp:2490-2576`), and
|
||
Klipper/Creality/Snapmaker derive tray ids at runtime. One PR per vendor; CI's `-f` scope
|
||
ratchets per vendor until tree-wide.
|
||
|
||
---
|
||
|
||
## 1. How filament_id actually works (verified)
|
||
|
||
### Consumers and scoping
|
||
|
||
All device integrations converge on one pipeline: device/agent sets `tray_info_idx` →
|
||
`DevAmsTray.setting_id` → `Sidebar::build_filament_ams_list` (`Plater.cpp:3423-3493`) →
|
||
`PresetBundle::sync_ams_list` / `get_ams_cobox_infos` (`PresetBundle.cpp:3112-3308`) match it
|
||
against filament presets.
|
||
|
||
| Ecosystem | Where the id comes from | Effect of changing a system id |
|
||
|---|---|---|
|
||
| BBL AMS | device-side (RFID / user tray setting), `DeviceManager.cpp:3823+` | breaks matching — **frozen by mandate** |
|
||
| Qidi box | built from device enums: `"QD_" + series + "_" + vendor + "_" + type_idx`, `QidiPrinterAgent.cpp:146-152`; needs an exactly-matching visible preset | breaks matching — **`QD_*` ids are a frozen device contract** |
|
||
| Creality CFS | runtime brand/type scoring returns current preset's id (`CrealityPrintAgent.cpp:46-118`) | invisible |
|
||
| Klipper (AFC / Happy Hare) | runtime `filament_id_by_type` (`MoonrakerPrinterAgent.cpp:808,936`) | invisible |
|
||
| Snapmaker | runtime color/vendor/type match (`SnapmakerPrinterAgent.cpp:22-64`) | invisible |
|
||
|
||
Matching is printer-scoped (`is_compatible`) in the AMS sync paths and all printer agents — this
|
||
is what makes the per-printer invariant sufficient there. But several consumers match
|
||
**globally by id alone**, first match wins:
|
||
|
||
- `get_filament_by_filament_id("")` — tray display name, `filament_is_support`,
|
||
`temperature_vitrification` warnings (`PresetBundle.cpp:690-733`; callers
|
||
`DevFilaBlackList.cpp:70`, `Plater.cpp:3453`, `SelectMachine.cpp:3560,4623`). The code
|
||
comment at `:695` states the assumption outright: an id maps to ONE material globally.
|
||
- `MachineObject::setting_id_to_type` (`DeviceManager.cpp:2538`), calibration-history name
|
||
lookup (`CaliHistoryDialog.cpp:62`), custom-filament cloud grouping (`Preset.cpp:2839`).
|
||
- The slicing pipeline itself: multi-nozzle filament grouping merges project filaments whose
|
||
`(filament_id, color)` match (`FilamentGroup.cpp:513-528` via `ToolOrdering.cpp:1164`).
|
||
|
||
**Consequence:** within one printer, duplicate ids break AMS matching (silent first-wins,
|
||
`find_if` at `PresetBundle.cpp:3132/3233`; the AMS tray-edit dialog even *hides* the second
|
||
preset, `AMSMaterialsSetting.cpp:894-897`). Across vendors, the same id on *different
|
||
materials* feeds wrong name/type/vitrification data to the global consumers and can merge
|
||
different materials into one nozzle group. Same id on the *same* material (e.g. `GFL99` =
|
||
Generic PLA in 29 vendors) is comparatively benign — those attributes agree.
|
||
|
||
### Identity machinery
|
||
|
||
- **Effective id resolution** (`PresetBundle.cpp:4842-5080`): own `filament_id` key → vendor
|
||
`filament_id_maps[inherits]` (file order in the vendor index is load-bearing) →
|
||
OrcaFilamentLibrary base-bundle map. An instantiated system filament that resolves *no* id is
|
||
a hard load error that discards the whole vendor bundle (`:5072`, throw at `:5141-5147`) —
|
||
so "missing id" cannot ship; what looked like 26 id-less Flashforge presets actually resolve
|
||
to OFL's `OGFL99`/`OGFG99` through the base-bundle fallback.
|
||
- **Two family-identity systems exist**: `filament_id` (device matching) and `alias` (name
|
||
before `" @"`). OFL shadowing is keyed on **alias**: `update_library_profile_excluded_from`
|
||
(`Preset.cpp:3684-3714`) hides an OFL preset (empty `compatible_printers` = compatible with
|
||
everything, `Preset.cpp:837`) on printers claimed by a same-alias vendor preset. There is
|
||
**no id-based shadowing**. A vendor preset that tunes an OFL generic but renames it
|
||
re-exposes the OFL preset and creates a live duplicate. The rule below aligns the two
|
||
systems: one family = one alias = one id.
|
||
- **User-custom id space**: user-created filaments get `"P" + md5(name)[0:7]` (8 chars,
|
||
`CreatePresetsDialog.cpp:533`), or *reuse a system id* when the base name matches an existing
|
||
preset (`:510-528`). `"null"` is used as a sentinel. Root user presets persist their id
|
||
forever; inheriting user presets re-derive it on every load.
|
||
|
||
## 2. The id landscape today
|
||
|
||
**Bambu's grammar** (derived from all 1970 BBL instantiated presets; BBL is internally clean —
|
||
181 id definitions, 0 duplicates):
|
||
|
||
- Classic `GF<LETTER><NN>`: letter = family (A Bambu-PLA, B ABS/ASA, C PC, G PETG/PCTG,
|
||
L third-party+generic PLA, N PA/PPA, P PP/PE, R misc, S support, T PET/PPS, U TPU).
|
||
Numbers: 00-49 branded ascending, 50-59 fiber-filled, 60-70 partner block, **95-99 generic
|
||
tier descending** (99 = the family's plain generic).
|
||
- Brand partners `GF<BRANDCODE><NNN>`: GFPM Polymaker, GFOT Overture, GFSNL SUNLU, GFNMK
|
||
Numakers. One id per product line; never per color, never per printer/nozzle/diameter.
|
||
- Structural rule: the id lives on the material's `@base`; every variant inherits it.
|
||
- Hardcoded in C++: `GFS00/GFS01` support check (`DeviceManager.cpp:4739`), per-family PA
|
||
defaults `GFU01/03/04` (`CalibUtils.cpp:54-75`) — `GF*` is Bambu's space, byte-frozen.
|
||
|
||
**Everything else is ad-hoc, invented by individual contributors and imitated** (full history
|
||
in §7): OFL's `O`-prefix mirrors (`OGFA00`; introduced 2025-03-31, commit `8c4a65e3e1`),
|
||
Tiertime/Afinia `GFx##_##` per-printer-line suffixes, SeeMeCNC per-nozzle ids, LH `LHF_pla`,
|
||
LONGER 11-char pseudo-GF ids, Anycubic ids **with spaces** (`"GFPLA Silk"`), Prusa ids that are
|
||
entire preset names (36 chars), and mass copy-paste of `GFL99/GFB99/GFG99` onto everything
|
||
(Qidi alone stamped `GFB99` into **317 files** across all materials). The only guidance that
|
||
ever existed was "≤ 8 chars" — enforced for BBL only (`orca_extra_profile_check.py:292,320`),
|
||
and the (now removed) profile wiki's own examples *taught* id copy-pasting.
|
||
|
||
**The damage, quantified** (audit reproduces validator 1256/1256):
|
||
|
||
| Ledger | Count |
|
||
|---|---|
|
||
| Within-vendor logical collision groups (validator `-f`) | **356** across 30 vendors (1256 printer-level errors) |
|
||
| OFL×vendor same-id groups (validator blind spot) | 42 — of which **10 are live** (alias mismatch defeats shadowing); 32 already neutralized by alias shadowing |
|
||
| OFL-internal: one id, several materials, visible on every printer | **16 ids** (e.g. `OEPLAB00` = 14 distinct Elegoo PLA products; `OGFL06` = eSUN PLA-Marble *and* Fiberon PETG-ESD) |
|
||
| Cross-vendor semantic collisions (same id, different materials) | **67 ids** (e.g. `GFU99` also covers a PEBA; Anycubic minted `GFL95` "Matte" ≠ Bambu `GFL95` "High Speed") |
|
||
|
||
Worst vendors by groups: Qidi 97, Flashforge 69, Elegoo 37, Prusa 31, Cubicon 17,
|
||
Anycubic 14, InfiMech 14, Snapmaker 11, Artillery 8, Creality 8, FlyingBear 8.
|
||
|
||
---
|
||
|
||
## 3. The rule (proposal)
|
||
|
||
Selected by a 3-design / 2-judge adversarial process (deterministic-mint won over
|
||
registry-grammar and structure-only on ambiguity-prevention, contributor simplicity, and
|
||
enforceability), then hardened by three adversarial review passes. This section is written as
|
||
the future authoring doc.
|
||
|
||
### 3.1 The one-question test
|
||
|
||
> **Would a user consider this a different spool product than anything already in the tree?**
|
||
> Different polymer, different sub-brand (Basic / Matte / Silk / HF), fiber-filled sibling, or
|
||
> a second selectable diameter → **new family, new id**. The same spool tuned for another
|
||
> printer or nozzle → **join the existing family** (inherit its `@base`, no id key). Tuning a
|
||
> generic material → **join the OFL family** (inherit the `Generic X @System` preset, keep the
|
||
> `Generic X` base name, add no id).
|
||
|
||
Same id / new id at a glance:
|
||
|
||
| Situation | id |
|
||
|---|---|
|
||
| Per-printer / per-nozzle variant of an existing material | same id (inherit, never write the key) |
|
||
| Sub-brand or product line (PLA vs PLA Matte vs PLA Silk vs PLA HF) | new id each |
|
||
| Color | never a new id |
|
||
| Second diameter selectable on the same printer (1.75 + 2.85) | sibling family, new id |
|
||
| "High-speed" tuned for a *different printer model* | same id (it's a printer variant) |
|
||
| "High-speed" selectable *alongside* the normal preset on one printer | new id (it's a product line) |
|
||
|
||
### 3.2 Structure
|
||
|
||
1. **One family = one root.** Each material family has root preset(s) (`instantiation:false`,
|
||
typically `<Family> @base`) and only roots carry the `filament_id` key. Instantiated
|
||
variants inherit a root and never write `filament_id`. (A family MAY have several roots —
|
||
e.g. Qidi's per-series bases — but they must all declare the *identical* id.)
|
||
2. **Family identity is declared, not name-derived.** Default: the family name is the
|
||
instantiated presets' base name (name with `/\s?@.*$/` stripped — note *optional* space,
|
||
because `Afinia PLA@HS`-style names exist). When vendor naming makes that ambiguous, the
|
||
root declares an explicit `"filament_family"` key that overrides derivation; tooling errors
|
||
loudly when a root's derived family differs from its children's.
|
||
3. **Within a family, variants' `compatible_printers` are pairwise disjoint** — that *is* the
|
||
PR #14459 invariant, enforced by the validator.
|
||
4. **Generics belong to OFL.** A vendor tuning `Generic PLA` inherits
|
||
`Generic PLA @System`, keeps the `Generic PLA` base name/alias (so alias shadowing excludes
|
||
the OFL preset on those printers, `Preset.cpp:3684`), sets non-empty `compatible_printers`,
|
||
and writes no id. A vendor-*branded* filament never rides a generic family id.
|
||
5. **Ids are immutable once shipped.** Renaming a family does not change its id (use
|
||
`renamed_from`). No id is ever recycled for a different material — stale ids live on in
|
||
user root presets and old 3mfs, and a recycled id would silently match the wrong material.
|
||
|
||
### 3.3 Minting — nobody invents ids
|
||
|
||
New family ids are computed, exactly like the `setting_id` precedent
|
||
(`scripts/assign_vendor_setting_ids.py` / `Slic3r::generate_preset_setting_id`):
|
||
|
||
```
|
||
filament_id = "OF" + base62_6( uuid5( NAMESPACE, "filament_family/<vendor>/<family>" ) )
|
||
```
|
||
|
||
8 chars total (satisfies the AMS length limit), same base62 derivation and a dedicated
|
||
namespace constant. On the astronomically rare collision with an existing id, the minter salts
|
||
the input (`.../1`, `/2`, …) until free; the result is simply frozen in the file.
|
||
|
||
- **Script path:** author commits the family with *no id anywhere*;
|
||
`python scripts/assign_filament_ids.py` inserts the minted id into the root(s). Idempotent;
|
||
never rewrites a valid existing id. A `--mint "<Vendor>/<Family>"` one-shot prints the id
|
||
without touching the tree.
|
||
- **No-script path:** CI fails with the exact line to paste:
|
||
`family "MyBrand PLA" (vendor X) needs filament_id "OFq3xT9k" in "MyBrand PLA @base.json"`.
|
||
|
||
**Reserved namespaces — never mint or hand-write into:**
|
||
|
||
| Space | Owner | Status |
|
||
|---|---|---|
|
||
| `GF*` | Bambu AMS/RFID catalog | byte-copies of authentic Bambu ids only, and only where a checked-in `shared_catalog` list sanctions the family (BBL bundle; OFL mirrors; byte-matching families in other vendors, e.g. Snapmaker's Fiberon) |
|
||
| `QD_*` | Qidi device protocol | frozen; Qidi-only; exempt from family-shape checks |
|
||
| `P[0-9A-Fa-f]{7}`, `"null"` | user-created custom filaments (`CreatePresetsDialog.cpp:533`) | never emitted for system presets (reserve case-insensitively) |
|
||
| everything already shipped | grandfather snapshot | frozen as-is (§5) |
|
||
|
||
Trade-off accepted: minted ids are opaque (`OFq3xT9k` carries no "PLA" mnemonic — the family
|
||
name in the same file provides that). The judges preferred this over a Bambu-style extended
|
||
grammar because it removes the "who allocates the next number" ceremony, cannot race between
|
||
concurrent PRs, and needs no registry maintenance. If mnemonic ids are strongly preferred, the
|
||
runner-up design (`<NSCODE><FAMLETTER><SEQ>` + registry file) is documented in the workflow
|
||
records; everything else in this plan is unchanged under either format.
|
||
|
||
### 3.4 What CI enforces (all vendors, ratcheted)
|
||
|
||
Extend `scripts/orca_extra_profile_check.py` (it imports the same mint function; setting_id
|
||
precedent) and the C++ validator:
|
||
|
||
1. **Format**: an id is valid iff `OF[0-9A-Za-z]{6}` **or** in the grandfather snapshot **or**
|
||
vendor==BBL **or** `QD_*` in Qidi. No whitespace/ASCII/length checks needed outside the
|
||
grandfather set — new ids are minted, and the grandfather set is closed.
|
||
2. **Uniqueness ratchet**: id→families multimap computed tree-wide; no id may acquire a family
|
||
claim not recorded in the snapshot (snapshot legalizes today's benign `GFL99`-style sharing;
|
||
new multi-claims are errors; `shared_catalog` entries are the sanctioned exception).
|
||
3. **Structure ratchet**: `filament_id` key only on roots; every instantiated filament must
|
||
resolve an effective id via the simulated loader walk; all members of one family resolve the
|
||
same id; **a preset may not declare an id different from its inherited effective id** (the
|
||
`Generic SBS` drift bug class). Pre-existing violations are snapshot-frozen; new ones error.
|
||
4. **Mint conformance**: an id new relative to the snapshot must equal the mint (or a salt
|
||
iteration); the error message prints the expected value.
|
||
5. **Stability**: an `(id, family)` pair on main may not change or vanish, *following
|
||
`renamed_from` chains* (so an honest rename passes), unless listed in a maintainer-gated
|
||
migrations file. Retired ids go to an append-only `retired_ids` ledger; a retired id may
|
||
never be defined again for any family.
|
||
6. **Alias hygiene for tuned generics**: a preset inheriting an OFL `Generic * @System` (with
|
||
no own id anywhere in its vendor chain) must keep the OFL base name and have non-empty
|
||
`compatible_printers` — error message names the rename as the cause.
|
||
7. **C++ validator**: extend `check_duplicate_filament_subtypes` (`PresetBundle.cpp:5654`) to
|
||
include OFL presets in every vendor's per-printer check, *minus* alias-excluded ones —
|
||
`m_excluded_from` is already populated in the validator context (`update_system_maps` at
|
||
`PresetBundle.cpp:2302`), so the 32 shadowed pairs won't false-positive and the 10 live ones
|
||
will be caught. Run `-f` per vendor in CI, widening as vendors are cleaned (§5).
|
||
8. **Runtime backstop** (one-line change): log a warning when the AMS-sync `find_if`
|
||
(`PresetBundle.cpp:3132/3233`) finds 2+ compatible presets for one id — the only layer that
|
||
can see side-loaded/forked bundles.
|
||
|
||
---
|
||
|
||
## 4. Error-pattern taxonomy (goal 2) — with counts and fixes
|
||
|
||
356 groups were classified by 37 agents reading the actual profiles, spot-checked
|
||
independently (1 substantive disagreement in 14 samples). Counts below fold the spot-check
|
||
corrections in. **Fix rule for all patterns: replacement id values are always freshly minted
|
||
`OF*`; the "which preset keeps the id" decision uses the precedence _Bambu-catalog material >
|
||
OFL generic family > family that historically introduced the id_.**
|
||
|
||
| # | Pattern | Groups | Fix |
|
||
|---|---|---|---|
|
||
| P1 | `copy_paste_id` — different materials share an id verbatim (task's error 1). Qidi's `GFB99`×317-files epidemic; Anycubic's three id "eras"; Peopoly `GFSL99` on ABS | ~167 | impostor families get minted ids on their roots; owner keeps the id |
|
||
| P2 | `wrong_inherits` / id-less product lines — a variant inherits another family's root (PR #14459's Panchroma case) or a product line never got its own root (Prusa HF; Flashforge's ~140-preset `FFG01` umbrella) | ~87 | create per-family roots with minted ids; re-point `inherits`; **never** delete the id-less shadow/base files — convert them (they carry real config: Flashforge `fdm_filament_pla` differs materially from OFL's) |
|
||
| P3 | `generic_family_overlap` — vendor-*branded* preset rides a generic family id via inheritance (task's error 2, generalized). Includes the 10 live OFL duplicates | ~68 | branded presets get minted family ids; true generic tunings instead adopt the OFL family *with matching alias* (rule 3.2.4); Sovol is the elegant case — just **delete** its wrong own-id lines and let OFL ids flow through inheritance |
|
||
| P4 | `overclaim_compat` — same material, broader variant claims a printer that a dedicated variant covers (the BBL H2DP pattern fixed in #14459) | ~24 | trim `compatible_printers` of the broader preset (Dremel, Cubicon `@base`s that are also instantiated, Wanhao France Bowden/Direct) |
|
||
| P5 | template-carried id — id declared on a shared settings template (`fdm_filament_*`, `fdm_filament_common`) so every family inheriting it collapses (Prusa, Ginger Additive, Snapmaker TPU base) | inside P1/P2 counts | move ids off templates onto family roots |
|
||
| P6 | OFL-internal collisions — 16 ids spanning several materials, visible on every printer (Elegoo blocks, Elas `OGFA00`×3, `OGFL06` polymer mismatch, `Generic PETG HF/PETG-CF @System` missing own ids) | 16 ids | fix inside OFL first (it's the base bundle every vendor resolves against) |
|
||
| P7 | alias-mismatch re-exposure — vendor tunes a generic under a different name, OFL preset resurfaces (Snapmaker `PolyTerra J1 PLA` vs OFL `PolyTerra PLA`) | 10 live | rename-to-alias where it's genuinely the same family, else mint |
|
||
| P8 | per-variant ids — no ambiguity, but family semantics broken: every variant has its own id (Prusa name-ids, SeeMeCNC nozzle suffixes, Afinia/Tiertime `_##`, iQ) so device matching can't identify the material across nozzles | ~210 presets | grandfather (they're unambiguous); converge opportunistically; document as anti-pattern |
|
||
| P9 | format violations — spaces (`"GFPLA Silk"`), >8 chars (LONGER, SeeMeCNC, LH), GF-shaped inventions (Anycubic `GFL93-97`, CoLiDo `GFA99`) | in the above | fixed as a byproduct of re-minting; snapshot freezes the unambiguous rest |
|
||
| P10 | cross-vendor semantic collisions — 67 ids meaning different materials in different vendors (dangerous via the global unscoped consumers, §1) | 67 ids | mostly eliminated by P1-P3 re-minting; the remaining same-material generic sharing is legalized by the snapshot |
|
||
| P11 | deliberate coexistence & data hygiene — Snapmaker "Benchy" demo presets (gated by `compatible_prints`, which the id check can't see) and a self-collision from duplicate `compatible_printers` entries | 4 | give demo presets own minted ids; dedupe list entries; add a lint for duplicate array entries |
|
||
|
||
New patterns beyond the two in the task (goal 2.3): P5-P11.
|
||
|
||
---
|
||
|
||
## 5. Migration plan
|
||
|
||
### Safety foundation (verified, §1/§7)
|
||
|
||
Safe: fresh never-used ids, family-consistent; trims of `compatible_printers`; inherits
|
||
re-pointing; OFL id changes (children re-derive). Unsafe: touching `BBL`/`QD_*`; recycling or
|
||
swapping ids; splitting a family's id; **deleting or renaming preset names** (user presets
|
||
whose `inherits` no longer resolves are dropped at load, `Preset.cpp:1687-1691`) — if a name
|
||
must go, `renamed_from` coverage is mandatory.
|
||
|
||
### Phases
|
||
|
||
0. **Land the rule + tooling first** (no profile changes): `scripts/assign_filament_ids.py`
|
||
(mint + insert + `--mint`), the extended `orca_extra_profile_check.py` checks in
|
||
snapshot-ratchet mode, the C++ validator OFL cross-check, the runtime warning, the rule doc.
|
||
Generate `filament_id_snapshot.json` (id→families multimap over main) and empty
|
||
`retired_ids.json` — both checked in.
|
||
1. **OFL first** (it's the base bundle every vendor resolves against): fix the 16 internal
|
||
collisions (Elas/eSUN/DREMC copy-pastes get mints; `Generic PETG HF/PETG-CF/PP-CF/PP-GF/
|
||
PE-CF/PLA Matte @System` get their own family roots+ids instead of collapsing into their
|
||
parent generic), keeping every current effective id that is unambiguous.
|
||
2. **Per-vendor PRs, worst-first**: Qidi → Flashforge → Elegoo → Prusa → Cubicon → Anycubic →
|
||
InfiMech → Snapmaker → the long tail (22 vendors, mostly 1-8 one-line fixes). Each PR flips
|
||
that vendor into CI's `-f` scope (`check_profiles.yml` currently `-v BBL -f`; append
|
||
vendors as they reach zero; when all are in, drop `-v` and run tree-wide).
|
||
3. **Delete the ratchet allowlists** once tree-wide zero holds; checks become hard rules for
|
||
everything born after the snapshot.
|
||
|
||
### Migration-script contract (from the adversarial pass — important)
|
||
|
||
- Consumes from the classification only: group membership, fix category, keep-id precedence,
|
||
inherits-repoint targets. **All replacement id values are recomputed via the mint** — id
|
||
literals in analysis notes (e.g. `GFA00_02`, `GFS98`, `GFG96`) are legacy-culture artifacts
|
||
and are ignored with a warning; assert no emitted id matches `^(GF|QD_|P[0-9A-Fa-f]{7}$)`.
|
||
- **Family-atomic**: re-idding any preset re-ids every same-family sibling in the same commit,
|
||
even siblings outside the collision group (FlyingBear `GFB99 @S1` vs `@Ghost7`); Prusa is
|
||
family-atomic per material (its HF/CF families span frozen `_N`-suffix ids — freeze what's
|
||
unambiguous, mint once per family for the colliding members).
|
||
- **Diff bound**: a vendor migration PR may only touch collision-group files + same-family
|
||
siblings of re-idded presets; every id that is per-printer-unambiguous today stays
|
||
byte-identical.
|
||
- **Config-equivalence gate**: dump every instantiated preset's flattened effective config on
|
||
main and on the PR head; the diff must be empty except `filament_id`/`inherits`/
|
||
`compatible_printers` edits the plan prescribes (this is what makes the Flashforge
|
||
shadow-file conversions safe: each id-less `fdm_filament_*` shadow becomes a named vendor
|
||
family root carrying its config byte-for-byte, children re-pointed, then the shadow name
|
||
retired).
|
||
- **No deletions**: redundant presets (Flashforge's byte-identical `Generic X`/`Flashforge X`
|
||
twins) are re-minted, not dropped; consolidation with `renamed_from` is a separate,
|
||
human-reviewed cleanup.
|
||
- Expected user impact: none for inheriting user presets, 3mfs, Klipper/Creality/Snapmaker
|
||
sync. Residue: user *root* presets that copied an old system id keep it forever
|
||
(AMS auto-match falls back to generic-by-type — low severity, unavoidable from the repo).
|
||
|
||
### Vendor-specific notes (from classification)
|
||
|
||
- **Qidi (97)**: three profile generations. `QD_*` generics are correct and frozen; the fix is
|
||
the brand families (Bambu/HATCHBOX/Overture/PolyLite/Tinmorry/QIDI-brand) that all carry
|
||
`GFB99/GFG99/GFL99`. Multi-root families are the norm (`...@Q2-Series` / `@Q2C-Series` /
|
||
`@X-Max 4-Series` bases) — same mint lands in every series root of one family.
|
||
- **Flashforge (69)**: two umbrellas (`FFG01` ~140 presets; `GFB99/GFG99/GFL99` G3U-era) +
|
||
OFL-riding branded presets + shadow-file conversion (above).
|
||
- **Elegoo (37)**: single mistake — every commercial variant inherits the material-class
|
||
`@base` (`E<MAT>B00`); mint one id per product line (Silk/Matte/PRO/Rapid/…), roots exist.
|
||
- **Prusa (31)**: HF product lines need their own roots; ids move off `fdm_filament_*`
|
||
templates; frozen name-shaped and `_N` ids stay.
|
||
- **Sovol (6)**: delete the wrong own-id lines; correct ids flow from OFL by inheritance.
|
||
- **Cubicon (17)**: `@base` presets are themselves instantiated + over-claiming; 9 file edits.
|
||
|
||
---
|
||
|
||
## 6. Concrete work items (PR-sized)
|
||
|
||
1. `scripts/assign_filament_ids.py` + mint function + tests (incl. the 14-group regression set
|
||
from the adversarial pass). *(new)*
|
||
2. `orca_extra_profile_check.py`: checks §3.4-1..6 in ratchet mode + snapshot/ledger files;
|
||
drop the BBL/OFL-only gate at `:292` and the OFL skip at `:601`. *(extend)*
|
||
3. C++ validator: OFL-aware `check_duplicate_filament_subtypes`; runtime ambiguity warning at
|
||
the two `find_if` sites. *(small)*
|
||
4. Rule documentation: `doc/developer-reference/filament_id.md` (recreate the path; wiki
|
||
cross-link) + profile-PR template checkbox ("new materials: no filament_id key anywhere; CI
|
||
prints the minted id"). *(new)*
|
||
5. OFL migration PR (phase 1).
|
||
6. Per-vendor migration PRs (phase 2), each widening CI `-f` scope.
|
||
|
||
The audit tooling from this analysis (loader-faithful resolver; reproduces the validator
|
||
1256/1256) is in this session's scratchpad (`audit_filament_ids.py`) and is the natural seed
|
||
for items 1-2.
|
||
|
||
## 7. Evidence & methodology
|
||
|
||
- Validator ground truth: `OrcaSlicer_profile_validator -f` tree-wide → 1256 errors; audit
|
||
script reproduces exactly (356 logical groups after dedup by (vendor, id, preset-set)).
|
||
- Code analysis: 5 parallel agents over runtime consumers, persistence/migration surface,
|
||
loader semantics, Bambu grammar, and convention history — all claims carry `file:line`.
|
||
- Classification: 37 agents (one per vendor chunk) reading actual profile JSONs; 14-sample
|
||
independent re-derivation found 1 substantive error (a Flashforge group mislabeled
|
||
`missing_id`; corrected — the loader resolves those ids from OFL).
|
||
- Design: 3 independent designs → 2 judges (both chose the deterministic mint; scores 58/50/56
|
||
and 60/54/56) → 3 adversarial attackers (35 scenarios; every `breaks` finding is folded into
|
||
§3.2-3.4/§5 as an amendment: declared families, multi-root support, ratchet-not-absolute
|
||
checks, no-deletion rule, shadow-file conversion, classification-id-literal quarantine,
|
||
diameter siblings, case-insensitive P-hex reservation, fork guidance).
|
||
- Key history: `OGF*` born 2025-03-31 (`8c4a65e3e1`, no PR); `_##` suffix born PR #9739;
|
||
8-char check born PR #9574; wiki (with the id-copy-paste example) removed 2025-11-24
|
||
(`f0d79b99eb`).
|
||
|
||
## 8. Open decisions for maintainers
|
||
|
||
1. **Id format**: opaque deterministic `OF*` mint (recommended, judges 2/2) vs mnemonic
|
||
registry grammar (runner-up). Everything else in the plan is format-agnostic.
|
||
2. **Multi-vendor brands** (Snapmaker ships Fiberon with authentic `GF*` ids): sanction via
|
||
`shared_catalog` (recommended, low churn) vs hoisting those families into OFL.
|
||
3. **Benchy-style demo presets**: mint ids per demo preset (recommended) vs teaching the
|
||
validator `compatible_prints` gating.
|
||
4. **Alias alignment**: fix the 10 live alias-mismatch OFL duplicates by rename-to-alias
|
||
(better long-term, needs `renamed_from`) vs minting vendor ids (safer, more ids).
|
||
5. **Where the rule doc lives**: in-repo `doc/` (recommended — CI messages need a stable link)
|
||
vs wiki-only.
|