mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-01 06:16:58 +00:00
225 lines
14 KiB
Markdown
225 lines
14 KiB
Markdown
# filament_id plan v4: dissolve the Qidi `QD_*` island
|
||
|
||
Supersedes §0.2 of `filament_id_plan_v3.md` (the "`QD_*` island — untouched, forever"
|
||
decision) and amends every v3 section that carved out `QD_*`. Everything else in v3 —
|
||
the mint rule, the succession ledger, the BBL island, checks 1–9 — stands as
|
||
implemented (v3.0–v3.2, all gates green at commit `f7c1b290fd`).
|
||
|
||
Decision driver: the island contradicts the catalog architecture v3 built. Qidi presets
|
||
carry 204 `QD_<series>_<vendor>_<typeidx>` ids over 264 declarations (49 families,
|
||
measured 2026-08-21) — one commercial product carries up to *five* ids (one per printer
|
||
series: `QIDI PLA Rapido` = `QD_0_1_1` … `QD_4_1_1`), which is exactly the
|
||
fragmentation v3 exists to remove. The island was frozen because Qidi's filament box
|
||
composes these ids from device enums and requires exactly-matching presets
|
||
(`QidiPrinterAgent.cpp:146-152`). But v3.0 shipped the machinery that makes freezing
|
||
unnecessary: the succession ledger already translates retired ids on resolution miss.
|
||
`QD_*` stops being a *preset id space* and becomes a *device protocol namespace*,
|
||
translated once at the agent edge.
|
||
|
||
## 0. The architecture change
|
||
|
||
Two tiers, **one** frozen island:
|
||
|
||
1. **BBL island — untouched, unchanged.** All v3 reasoning holds (device/RFID/cloud
|
||
contract is external and opaque).
|
||
2. **Everything else converges on OFL product ids — now including Qidi.** All 204
|
||
`QD_*` ids re-mint from their family triples and gain **retired** ledger entries
|
||
(not cross-island hints: after dissolution there is no island to own them, and
|
||
check 4's "never again" is exactly the guard we want against upstream re-adding
|
||
them). The box keeps working because `QidiPrinterAgent` resolves its composed
|
||
`QD_*` id through `resolve_filament_id_succession()` on miss — the same mechanism
|
||
every other retired id already uses.
|
||
|
||
Why retire rather than hint (the one real design choice here): hints exist for ids a
|
||
*foreign catalog* owns and may legitimately re-ship (`GF*`). Post-dissolution, nothing
|
||
may ever re-ship a `QD_*` id in the profile tree — the device composes them at
|
||
runtime, the tree translates them. Retired entries make CI enforce that permanently
|
||
(`--update-snapshot` refuses resurrections, check 4 refuses occurrences); hints would
|
||
permit re-shipping, which is now always a regression. The QD→family mapping is
|
||
strictly 1:1 (verified: no `QD_*` id is claimed by more than one family), so the
|
||
mode-rule successor is unambiguous for every entry.
|
||
|
||
## 1. What the dissolution consists of
|
||
|
||
Three independent work packages, ordered for bisectability:
|
||
|
||
- **(A) Re-converge the drifted tree** — prerequisite, not Qidi-specific. The
|
||
2026-07 main merge (`f3fa3a34bd`) brought upstream vendor updates in pre-v3 style:
|
||
`--check` currently exits with **243 errors** (123 unsanctioned new ids, 46 new
|
||
instantiated own-key presets, 31 override drifts, 18 reserved-space claims, 5
|
||
hint-keys re-declared — Qidi re-added `GFB99`/`GFG99`/`GFL99`, Snapmaker re-added
|
||
`GFG96`/`GFU99` and even four *retired* ids incl. `OGFL99`, plus Snapmaker U1
|
||
triple errors and a GreenGate3D rename). This is the standing WF-B maintenance
|
||
pass; the dissolution's gates cannot go green on a red base.
|
||
- **(B) C++ succession hook in the Qidi agent** — safe to land before any data
|
||
changes (pure miss-path: while `QD_*` presets still exist, the hook never fires).
|
||
- **(C) The dissolution proper** — tooling flip + Qidi data fixes + re-mint +
|
||
snapshot/ledger regeneration, one vendor worksheet in the v3.2 mold.
|
||
|
||
## 2. Runtime translation (work package B)
|
||
|
||
`QidiPrinterAgent.cpp:183-192` currently: compose `setting_id` → keep it if a visible
|
||
base preset declares it → else degrade to `filament_id_by_type(tray_type)` (i.e. every
|
||
QIDI-brand box slot silently becomes Generic once the ids re-mint). Insert the
|
||
succession walk between those two steps:
|
||
|
||
```cpp
|
||
} else if (!setting_id.empty() && has_visible_base_preset(bundle->filaments, setting_id)) {
|
||
tray.tray_info_idx = setting_id;
|
||
} else {
|
||
// Retired QD_* protocol ids forward to their minted successors via the shipped ledger.
|
||
const std::string successor = setting_id.empty() ? std::string()
|
||
: resolve_filament_id_succession(setting_id);
|
||
if (!successor.empty() && has_visible_base_preset(bundle->filaments, successor))
|
||
tray.tray_info_idx = successor;
|
||
else
|
||
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
|
||
}
|
||
```
|
||
|
||
`resolve_filament_id_succession` is `Preset.hpp:119` (loads once, cycle-guarded,
|
||
empty-safe) — no new includes needed beyond what the file already reaches through
|
||
`PresetBundle`. This one hook covers both composition paths: the numeric-series
|
||
`build_setting_id` lambda *and* the non-numeric fallback
|
||
`map_filament_type_to_setting_id` (`:325-342`), whose four hardcoded returns
|
||
(`QD_1_0_1`/`_11`/`_41`/`_50` = Generic PLA/ABS/PETG/TPU 95A) become ledger keys in
|
||
package C. Keep that function as-is but extend its comment: the returned ids are
|
||
retired ledger keys by design, and `scripts/tests/test_filament_id.py` parses the
|
||
initializer (see §4 tests) — the Moonraker treatment (`MoonrakerPrinterAgent.cpp:
|
||
619-627`) of replacing the table with name lookups was considered and not taken: the
|
||
table already routes through the same ledger as the composed ids, and two translation
|
||
mechanisms in one agent is worse than one.
|
||
|
||
Tests (same commit):
|
||
|
||
- `tests/libslic3r/test_filament_id_succession.cpp`: add a section asserting a
|
||
`QD_`-shaped key forwards like any other (`{"QD_2_1_11", "OFnew001"}` resolves to
|
||
`"OFnew001"`) — pins that the walk is prefix-agnostic.
|
||
- `scripts/tests/test_filament_id.py`: new test parsing the four `QD_` literals out of
|
||
`QidiPrinterAgent.cpp::map_filament_type_to_setting_id` (mirror the parser in
|
||
`scripts/test_moonraker_lane_data.py`) and asserting each is a ledger key whose
|
||
chain terminates at a live tree id. **Add it marked expected-fail/skipped until
|
||
package C lands, then flip it on** — it is the permanent code↔ledger lockstep guard.
|
||
|
||
## 3. Tooling and validation flip (work package C, first commit)
|
||
|
||
All in `scripts/assign_filament_ids.py`; every touched line measured 2026-08-21:
|
||
|
||
- `is_island_declaration` (`:327-329`) → `return vendor == "BBL"`. This single change
|
||
pulls every `QD_*` declarer into the triple bookkeeping (`:385`) and thus into
|
||
checks 3 and 8, into `--remint`'s domain (`:1464`), and out of the hint-key
|
||
tolerances (`:877`, `:1100`).
|
||
- `reserved_space_owner` (`:596-604`): `QD_*` returns `(True, None)` — reserved,
|
||
ownerless, exactly like the P-hex/user-custom space. Consequences, all wanted:
|
||
check 6 refuses any future vendor claim; the `--update-snapshot` sanction gate
|
||
(`:938-963`) refuses new `QD_*` ids outright; the vanish path (`:994`) routes
|
||
`QD_*` to **retired** (owner `None` ≠ island), not released-with-hint. Update the
|
||
two message sites that render `owner is None` as "reserved for user-custom presets"
|
||
(`:628` docstring, `:958`, and check 6's copy) to name the space generically or
|
||
special-case `QD_*` ("Qidi device protocol; dissolved island — retired, never
|
||
declarable").
|
||
- Check 1 (`:667-668`): delete the `vendor == "Qidi" and fid.startswith("QD_")`
|
||
exemption.
|
||
- Module docstring (`:27-45`): rewrite the `QD_*` bullet — reserved space stays
|
||
listed, but as "device protocol namespace, translated via the succession ledger;
|
||
dissolved as a catalog island in v4, may never be declared".
|
||
- `scripts/tests/test_filament_id.py`: update the three island assertions —
|
||
`reserved_space_owner("QD_X4_PLA")` → `(True, None)` (`:403`),
|
||
`is_island_declaration("Qidi", "QD_X4_PLA")` → `False` (`:413`), and the
|
||
reserved-space message case (`:570`).
|
||
|
||
No changes to the ledger schema, the C++ checks, `--retire` (post-flip it accepts
|
||
`QD_*` olds automatically — they are non-island now), or the validator: check 3's
|
||
"non-BBL, non-`QD_*`" phrasing in v3 §3 was always implemented as "non-island", so the
|
||
flip *is* the spec change.
|
||
|
||
## 4. Migration phases
|
||
|
||
**v4.0 — re-converge the drifted tree (package A).** Per-vendor WF-B worksheets over
|
||
the 243 errors: Snapmaker U1 (triple divergence `Generic|Snapmaker|snapmaker`, empty
|
||
vendors, four resurrected retired ids — these force re-mints since retirement is
|
||
permanent), BBL/addnorth `GF_AN*` overrides (BBL island: grandfather via snapshot,
|
||
they are BBL-internal), Qidi/Snapmaker re-declared hint keys (re-mint those declarers,
|
||
the check's own prescription), GreenGate3D rename, then `--update-snapshot` (new
|
||
upstream `QD_*` ids sanction cleanly — the island is still intact in this phase) and
|
||
the full v3 §5 gate battery. Bump `version` in every touched
|
||
`resources/profiles/<Vendor>.json`. **Do not start v4.2 until `--check` exits 0.**
|
||
|
||
**v4.1 — the agent hook (package B).** §2 as written; `libslic3r_tests` +
|
||
`scripts/tests/test_filament_id.py` green; behavior-neutral by construction (no `QD_*`
|
||
ledger entries exist yet).
|
||
|
||
**v4.2 — the dissolution (package C).** One Qidi worksheet, v3.2 machinery:
|
||
|
||
1. Tooling flip commit (§3). `--check` now reports the Qidi island as
|
||
non-conformant — expected, red only between commits of this phase.
|
||
2. Data fixes, before any re-mint (the v3 "W3 lands first" lesson — type/vendor are
|
||
key components): add `filament_vendor: ["QIDI"]` at each QIDI-brand family's
|
||
inheritance apex so all declarers resolve it (39 of 49 families currently resolve
|
||
none — check 8a would refuse the mint). Verify the six Generic families
|
||
(`Generic PLA/ABS/PETG/PC/TPU 95A/PLA Silk`) resolve triples identical to their
|
||
OFL counterparts (vendor `Generic`, OFL's `filament_type`) so they *converge onto
|
||
the OFL ids* by triple math — the whole point; any mismatch is a W3-style data fix
|
||
here, not a fork. No `inherits`/`compatible_printers` re-pointing anywhere: the
|
||
series intermediates (`Generic PLA@Q2-Series` etc., all `instantiation: false`)
|
||
simply keep declarations whose values become the OFL ids.
|
||
3. `python scripts/assign_filament_ids.py --remint Qidi` — rewrites all 264
|
||
declarations in place to their family mints (same triple ⇒ same id across a
|
||
family's series intermediates and per-nozzle declarers; convergence with OFL ids
|
||
is legal by design, `want_id` already permits same-triple collisions `:1447`).
|
||
4. `--update-snapshot` — retires every vanished `QD_*` id with its mode-rule
|
||
successor. **Audit the ledger diff: all ~204 new entries must be `QD_*`→non-null.**
|
||
A null successor (possible only for a declared-only id with zero instantiated
|
||
claims) gets an explicit `--retire "QD_x=OFy"` with the family's minted id.
|
||
5. Un-skip the §2 lockstep test. Bump `resources/profiles/Qidi.json` version. Full
|
||
gate battery (§5).
|
||
|
||
Known residue, accepted: `QIDI PC-ABS-FR` (series 1–2) vs `QIDI PC/ABS-FR`
|
||
(series 3–4) are two preset-name families for one product → two ids. Renames were
|
||
ruled out in v3 (`renamed_from` rejected for migrations); if upstream ever unifies the
|
||
name, content-addressing re-ids and the ledger absorbs it — self-healing, no action
|
||
now.
|
||
|
||
**v4.3 — OFL consolidation (unchanged).** v3.3 stays optional and id-stable; QIDI-brand
|
||
families are vendor-unique and stay in the Qidi bundle.
|
||
|
||
## 5. Gates (per phase, delta from v3 §5)
|
||
|
||
Unchanged battery: `orca_extra_profile_check.py` exit 0, `assign_filament_ids.py
|
||
--check` exit 0, `scripts/tests/test_filament_id.py` all green, `libslic3r_tests`
|
||
green, validator `-l 2` tree-wide + `-f` tree-wide + `-v Qidi` exit 0, custom-preset
|
||
fixture archives (v4.2 touches no preset visibility, but they are cheap insurance —
|
||
run them for v4.0, which touches instantiation-adjacent upstream drift), flatten
|
||
config-equivalence. New for v4.2: equivalence diff may contain **only** `filament_id`
|
||
value changes (QD→OF) and the added `filament_vendor` keys on QIDI-brand families;
|
||
ledger diff audit per §4.4; the map-literal lockstep test. Manual release-checklist
|
||
item: Qidi box smoke test — slot holding a QIDI-brand material must surface the brand
|
||
preset (not Generic) on an updated client, via the memory-documented local Klipper
|
||
test rig.
|
||
|
||
## 6. Accepted costs (explicit, new relative to v3)
|
||
|
||
- **Every Qidi box slot resolves through the ledger miss-path forever** (one hash-map
|
||
walk per slot per status poll — negligible, and structurally identical to how
|
||
every retired id already resolves). The QD→OF translation table is maintained by
|
||
the retirement machinery, not by hand.
|
||
- **Older Orca clients** (pre-v4 profiles) paired with re-minted profile trees lose
|
||
QIDI-brand slot matching (they look up `QD_*` and fall back generic-by-type — the
|
||
degradation the hook removes for updated clients). Same one-time field-transition
|
||
shape v3 §6 already accepted for AMS/AFC ids.
|
||
- **Two ids for PC-ABS-FR** until upstream unifies the family name (§4 residue).
|
||
|
||
## 7. Evidence
|
||
|
||
Measured on this tree 2026-08-21 unless cited to v3: 204 distinct `QD_*` ids / 264
|
||
declaring presets / 49 families, QD→family strictly 1:1, per-series id sets
|
||
(`QD_0..4_1_1` = `QIDI PLA Rapido` etc.); 39 families resolve no `filament_vendor`;
|
||
declarations live on `instantiation:false` series intermediates (Q2/Q2C/X5/X4) and
|
||
per-nozzle X-Plus-4 presets; composition + miss-fallback at
|
||
`QidiPrinterAgent.cpp:146-152, 183-192`, hardcoded fallback table `:325-342`;
|
||
succession helpers `Preset.hpp:109-119`; ledger = 575 retired + 194 hints, zero `QD_*`
|
||
entries; snapshot holds 160 of the 204 (the 44 newcomers are post-merge drift);
|
||
`--check` = 243 errors, categorized in §1(A); island exemption mechanics at
|
||
`assign_filament_ids.py:327-329, 385, 596-604, 667, 877, 994, 1464, 1514`; `--remint`
|
||
same-triple convergence guard `:1447-1453`; retirement-permanence gate `:965-976`.
|