mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-09 02:06:54 +00:00
501 lines
34 KiB
Markdown
501 lines
34 KiB
Markdown
# Filament IDs (`filament_id`)
|
|
|
|
`filament_id` identifies one **filament product**: one named spool product = one id, shared by
|
|
all of that product's per-printer / per-nozzle variants, in every profile bundle that ships it.
|
|
Devices use it to match a physical spool or tray to a filament preset. It is never per-color,
|
|
per-printer, per-nozzle, or per-preset (per-preset identity is `setting_id`), and it is never
|
|
per-bundle either — PolyLite PLA carries the same id whether the preset lives in the
|
|
OrcaFilamentLibrary (OFL), Qidi, or Snapmaker bundle. The granularity is the name on the spool,
|
|
not the brand behind it: `AAA PLA Lite` and `AAA PLA Pro` are two filaments with two ids, not
|
|
variants of one.
|
|
|
|
**How it is generated:** an id is computed, never invented. `scripts/orca_id_tool.py`
|
|
mints it as a deterministic hash of the product's identity — the triple
|
|
`(filament_vendor, filament_type, filament name)`, where the filament name is the preset name
|
|
with its `@...` variant suffix stripped — producing an 8-character `OF*` code that is the
|
|
same for that product in every bundle, in every PR, on every machine. For example, Polymaker's
|
|
PolyLite PLA presets (`PolyLite PLA @base`, `PolyLite PLA@Q2-Series`, …) resolve
|
|
`filament_vendor` `Polymaker`, `filament_type` `PLA`, and filament name `PolyLite PLA`; hashing
|
|
`filament_product/Polymaker/PLA/PolyLite PLA` yields `OF5CgdDq`, and that is the id the
|
|
OrcaFilamentLibrary, OrcaArena, Qidi, and Snapmaker bundles all arrive at independently
|
|
(derivation details in the Minting section).
|
|
|
|
**How it is used:** at runtime the id is the join key between hardware and profiles.
|
|
When a printer reports what a tray holds (Bambu AMS, Qidi box, Creality CFS,
|
|
Klipper, Snapmaker), OrcaSlicer matches the reported id against the filament presets
|
|
compatible with that printer to select the right profile; other features — tray display
|
|
names, support-material detection, vitrification warnings, multi-nozzle filament grouping —
|
|
look up material properties by id alone. An id that changes is not forwarded anywhere: a
|
|
tray or record still holding the old value falls back to matching by material type until the
|
|
user re-selects the filament, so identity changes are made deliberately and rarely.
|
|
|
|
This page is the rule for authoring `filament_id` in system profiles
|
|
(`resources/profiles/**`). CI enforces everything below; the short version is:
|
|
|
|
> [!IMPORTANT]
|
|
> **Never write a `filament_id` value by hand.** A new filament gets its id from
|
|
> `python scripts/orca_id_tool.py --generate`; one already in the tree has one — inherit it.
|
|
|
|
## The design, in two pieces
|
|
|
|
Because several consumers match **globally by id alone, first hit wins** (see the next
|
|
section), any two materials sharing one id feed wrong data somewhere — a wrong tray name, a
|
|
wrong support-material flag, a wrong nozzle grouping — and inside one printer a duplicated id
|
|
makes AMS spool matching a coin toss. Hand-written ids produce such collisions constantly, so
|
|
the system is built to make them impossible:
|
|
|
|
1. **Deterministic minting.** An id is a pure hash of the product's identity — no registry to
|
|
maintain, no next-free-number ceremony, no way for two concurrent PRs to race for the same
|
|
number, and no way to get it wrong by hand, because you never write it by hand.
|
|
2. **A sanctioned snapshot.** The complete id landscape derived from the tree must equal
|
|
`scripts/filament_id_snapshot.json` exactly, so every change to ids, claims (which bundles
|
|
ship which id, and for which filament), or product identity surfaces as a reviewable diff to
|
|
one file — the maintainer gate.
|
|
|
|
## Who consumes the id
|
|
|
|
The canonical consumer is tray-to-preset matching: a device reports a tray material id
|
|
(`tray_info_idx`), and the shared matching pipeline (`PresetBundle::sync_ams_list` and
|
|
friends) resolves it to a preset. The matcher is printer-scoped and first-match-wins:
|
|
scanning only compatible root presets — system roots plus user-made custom filaments, which
|
|
are user roots carrying their own `P*` ids; a preset derived from another resolves through
|
|
its root and never matches directly — it picks the first one whose `filament_id` equals the
|
|
tray's. On a miss it falls back by filament type: a system `Generic <type>` preset
|
|
(matched by name, then by type similarity), else the slot's previous selection, else any compatible system generic or,
|
|
failing that, any compatible system preset, else the slot is skipped — every fallback
|
|
selection surfaces a user-visible notice.
|
|
|
|
Today only the Bambu AMS integration follows this pattern end to end — the device itself
|
|
reports the id, `BBLPrinterAgent` translates it out of Bambu's catalog into ours, and the
|
|
pipeline does all the matching. The other device integrations still synthesize a preset id
|
|
client-side in their agents (by type, brand, or color lookups against the loaded presets)
|
|
before the pipeline runs; they are intended to converge on the same pattern, with the
|
|
device-reported tray material id flowing through the shared matcher.
|
|
|
|
| Ecosystem | Where the tray id comes from today |
|
|
| --- | --- |
|
|
| Bambu AMS | the device itself (RFID / user tray setting), in Bambu's own `GF*` catalog; `BBLPrinterAgent` rewrites it into our id before the matcher sees it (see [The Bambu catalog map](#the-bambu-catalog-map)) |
|
|
| Qidi box | composed at runtime as `QD_<series>_<vendor>_<typeidx>` — vendor and type indices from the device's per-slot saved variables, the series digit inferred client-side from the printer model/name. No preset carries a `QD_*` value, so the slot currently resolves by filament type; mapping the composed id onto the filament's minted id belongs in the agent |
|
|
| Creality CFS | runtime brand/type scoring returns the winning preset's id |
|
|
| Klipper (AFC / Happy Hare) | runtime lookup by filament type |
|
|
| Snapmaker | runtime color/vendor/type match |
|
|
|
|
Tray-to-preset matching is printer-scoped, but **several consumers match globally by id alone,
|
|
first hit wins**: tray display names, `filament_is_support`, vitrification warnings, and
|
|
multi-nozzle filament grouping in the slicing pipeline (`FilamentGroup::try_merge_filaments`
|
|
merges plate slots sharing one `(filament_id, color)` pair, with matching
|
|
extruder-printability, onto one nozzle group; the engine is implemented but no grouping path
|
|
calls it yet).
|
|
Two *different* materials sharing one id
|
|
feed wrong data to those consumers even when the presets live in different vendors — so
|
|
cross-material id sharing is never safe. Within one printer, duplicate ids break AMS matching:
|
|
the matcher picks whichever preset loads first (it now logs an "Ambiguous AMS filament match"
|
|
warning, but the pick is still arbitrary) and the tray-edit dialog, which lists one entry per
|
|
id, hides the second preset entirely. The profile validator's `-f` check
|
|
(`check_filament_subtypes` → `PresetBundle::check_duplicate_filament_subtypes`) rejects this
|
|
per printer, and CI runs it tree-wide.
|
|
|
|
Two more consumer-side facts worth knowing:
|
|
|
|
- The machine-facing dialogs (AMS tray edit, AMS dry control, calibration history, extrusion
|
|
calibration) offer the filaments a connected printer can use by the same compatibility rule
|
|
the plater uses (an empty `compatible_printers` means *every* printer). Alias shadowing
|
|
still applies: a vendor's same-name profile supersedes the library generic. That is what
|
|
puts Orca Filament Library materials in those lists — deduplicated to one entry per
|
|
`filament_id` in the AMS and calibration-history dialogs, while extrusion calibration
|
|
deliberately lists every matching preset by full name.
|
|
- The id is load-bearing at startup: an instantiated system filament (one marked
|
|
`"instantiation": "true"` — see the structure rules) that resolves **no**
|
|
`filament_id` anywhere in its `inherits` chain is a hard load error in the C++ loader
|
|
(`Can not find filament_id for <name>`) that discards the entire vendor bundle (for the
|
|
OrcaFilamentLibrary itself the failure is messier: library presets loaded before the
|
|
failing one survive, and every vendor bundle whose filaments inherit from the library is
|
|
then discarded for want of a base). CI's structure check catches this before it ships.
|
|
|
|
## Do I need a new id? 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 filament, new id**. The same spool tuned for another printer
|
|
or nozzle → **join the existing filament** (keep its base name and inherit it; no id
|
|
key needed). Tuning a generic material → **join the OrcaFilamentLibrary filament** (inherit
|
|
`Generic X @System` and keep the `Generic X` base name; no id key needed).
|
|
|
|
| Situation | id |
|
|
| --- | --- |
|
|
| Per-printer / per-nozzle variant of an existing material | same id (inherit it) |
|
|
| 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 of the same product (1.75 + 2.85) | sibling filament, new id |
|
|
| "High-speed" tuned for a *different printer model* | same id (it is a printer variant) |
|
|
| "High-speed" selectable *alongside* the normal preset on one printer | new id (it is a product line) |
|
|
|
|
## Structure rules
|
|
|
|
1. **Every preset carries the id of its own product, wherever it gets it from.** The id is a
|
|
function of the preset's own triple (rule 5), and `inherits` carries settings, never
|
|
identity. So a preset may declare the key itself or inherit it from any ancestor — a
|
|
`<Filament> @base` root, a real (instantiated) preset of the same filament, an
|
|
OrcaFilamentLibrary preset — and CI checks one thing: the id it ends up with equals the
|
|
mint of *its* triple. The usual shape is one `@base` root (`"instantiation": "false"`)
|
|
declaring the key and the per-printer variants inheriting it; a filament may have several
|
|
roots — Qidi's PolyLite PLA has four per-series roots (`PolyLite PLA@Q2-Series`,
|
|
`@Q2C-Series`, `@X-Max 4-Series`, `@X-Plus 5-Series`) — which then all declare the identical
|
|
id. A branded filament that borrows a generic's settings (`Flashforge ABS Basic @FF C5`
|
|
inherits `Generic ABS @System`) declares its own id, because its triple is its own.
|
|
2. **The filament name is the base name**: the preset name with everything from the first
|
|
(optionally space-preceded) `@` stripped. `MyBrand PLA @Orca 3D Fuse1` and `MyBrand PLA@HS`
|
|
are both the filament `MyBrand PLA`.
|
|
3. **Within one filament, variants' `compatible_printers` are pairwise disjoint** — per printer,
|
|
at most one compatible instantiated preset per id, or AMS matching turns ambiguous. The
|
|
C++ validator's `-f` check enforces this.
|
|
4. **Generics belong to OrcaFilamentLibrary.** A vendor tuning a generic material inherits
|
|
`Generic X @System`, keeps the `Generic X` base name (that alias is what hides the library
|
|
preset on your printers, and it is what makes its triple — and so its id — the library's)
|
|
and sets a non-empty `compatible_printers` — e.g. `Generic PLA @Sovol SV08 MAX` inherits
|
|
`Generic PLA @System` and lists three Sovol nozzles. Renaming such a preset makes it a
|
|
different product by rule 5, so it then needs its own id.
|
|
5. **Ids follow the product identity.** The id is a pure function of the product triple
|
|
`(filament_vendor, filament_type, filament name)`, so correcting any of them re-mints the id
|
|
**by design**, applied by `--generate` (preview with `--dry-run`, confine with `--vendor`) and
|
|
gated by the `--update-snapshot` diff; the exact sequence is in the FAQ. Nothing forwards
|
|
the old value, so anything outside the tree that stored it — a device tray, a calibration
|
|
record, a saved project — falls back to matching by filament type until the user re-selects
|
|
the filament. Re-mint deliberately, and only to fix a genuinely wrong identity.
|
|
(`renamed_from` still gates preset-*name* compatibility, as before.)
|
|
|
|
## Minting — nobody invents ids
|
|
|
|
New ids are deterministic, computed exactly like the `setting_id` precedent
|
|
(the `setting_id` half of `scripts/orca_id_tool.py`):
|
|
|
|
```text
|
|
FILAMENT_ID_NAMESPACE = uuid5(setting-id NAMESPACE, "filament_id")
|
|
= c4d3ff49-4c32-5534-a3e3-00894157ab97
|
|
filament_id = "OF" + base62_6( uuid5(FILAMENT_ID_NAMESPACE,
|
|
"filament_product/<filament_vendor>/<filament_type>/<filament_name>") )
|
|
```
|
|
|
|
`base62_6` is the low 6 base62 digits (alphabet `0-9A-Za-z`) of the UUID taken as a big-endian
|
|
integer, most-significant digit first; with the `OF` prefix the full id is 8 chars, within the
|
|
AMS length limit. The triple comes from the root preset's *flattened* config:
|
|
`<filament_vendor>` is the filament
|
|
**manufacturer** (`"Polymaker"`, or `"Generic"` for generics — never the printer brand),
|
|
`<filament_type>` the material type, `<filament_name>` the root's base name; the two config
|
|
values are inheritable list options and the first element counts.
|
|
|
|
Content-addressing on that triple is what makes the whole system converge. The key contains no
|
|
bundle name, so the same product mints the same id in every bundle — moving a filament into
|
|
OrcaFilamentLibrary never changes its id, and two vendors independently shipping the same
|
|
product arrive at the same id without coordinating. `Polymaker/PLA/PolyLite PLA` mints
|
|
`OF5CgdDq`, and that one id is declared by the OrcaFilamentLibrary, OrcaArena, Qidi, and
|
|
Snapmaker bundles alike; the OFL generic `Generic/PLA/Generic PLA` mints `OFDSrzZ8`, claimed
|
|
by 35 bundles — most by independent declarations converging on the same mint, the rest
|
|
purely through inheritance from the OFL preset.
|
|
|
|
On the rare collision with an existing id, the minter salts the input (`…/1`,
|
|
`…/2`, …) until free, and the result is frozen in the profile file. Salting is also used
|
|
deliberately: a *salt split* keeps two presets of one product on distinct ids where a single
|
|
id would be AMS-ambiguous on the same printer — the "selectable alongside" situation from the
|
|
table above, resolved without inventing a second filament name. The tooling recognizes salt
|
|
iterations of a triple as conformant and preserves such splits across re-mints.
|
|
|
|
Workflow for a new filament:
|
|
|
|
```bash
|
|
# 1. Author the filament with NO filament_id key anywhere.
|
|
python scripts/orca_id_tool.py --dry-run # 2. preview the ids — writes nothing
|
|
python scripts/orca_id_tool.py --generate # 3. apply them to the profile file(s)
|
|
python scripts/orca_id_tool.py --update-snapshot # 4. record the new claims in the snapshot
|
|
python scripts/orca_id_tool.py --check # 5. validate the filament_id state
|
|
python scripts/orca_extra_profile_check.py # 6. ...and everything else CI checks
|
|
# 7. Commit the profile edits together with scripts/filament_id_snapshot.json, for review.
|
|
```
|
|
|
|
`--generate` makes every filament's id equal the mint of its own
|
|
`(filament_vendor, filament_type, filament name)` triple: it inserts one where an instantiated
|
|
filament resolves none, and re-derives one that does not match. A preset that *inherits* a
|
|
mismatching id is the one case left to the author — check 3b names it, and the fix is to inherit
|
|
a preset of the same filament or to give the preset its own key. It converges on an id the same
|
|
product already holds and salts only past ids *other* products hold, so an id already equal to a
|
|
salt iteration of its own triple is left alone and deliberate salt splits survive. The same run
|
|
assigns `generate_preset_setting_id(vendor, type, name)` to every instantiated filament, process
|
|
and machine preset of every vendor except BBL, which keeps its authoritative `G*` ids, strips
|
|
`setting_id` from base profiles, and fixes the misspelled `settings_id` key — dropped, or, for
|
|
BBL, whose ids have no formula to fall back on, restored under the correct name. It is idempotent and
|
|
byte-preserving (indentation, BOM, and line endings intact, every edited file re-parsed to fail
|
|
loudly), and a no-op on a tree that already passes `scripts/orca_extra_profile_check.py` — the
|
|
check CI runs over both id kinds, of which `--check` is the `filament_id` half.
|
|
|
|
- `--filament-id` limits the run to `filament_id`.
|
|
- `--setting-id` limits the run to `setting_id`. The two exclude each other; pass neither to
|
|
write both.
|
|
- `--vendor VENDOR` confines the run to that bundle; repeatable. The ids are still derived
|
|
tree-wide, so a narrowed run writes exactly what a full one would — and reports any duplicate
|
|
it was not allowed to clear, since only a run covering both bundles can.
|
|
- `--dry-run` reports what `--generate` would do and writes nothing; with no mode of its own it
|
|
implies `--generate`, so `--dry-run --vendor <Vendor>` previews just that bundle.
|
|
- `--profiles DIR` points the tooling at a different profile tree (default
|
|
`resources/profiles`). `--check` and `--update-snapshot` read and write the sanctioned state of
|
|
the tree they are given, so pointing them elsewhere needs `--snapshot PATH` for that tree too —
|
|
`scripts/filament_id_snapshot.json` describes `resources/profiles` and no other tree.
|
|
|
|
**Identity fixes need no separate mode.** `--generate` re-derives an id that no longer matches its
|
|
triple exactly the way it fills in a missing one, so a rename or a `filament_vendor` /
|
|
`filament_type` correction is just: fix the config, run `--generate` (confine it with `--vendor`,
|
|
preview it with `--dry-run`), then `--update-snapshot` and review the diff.
|
|
|
|
If you skip the tooling, CI fails and prints the remedy: the expected id for your filament and
|
|
the instruction to run `python scripts/orca_id_tool.py --generate`; once the id is minted, the
|
|
snapshot checks likewise point at `--update-snapshot` and tell you to commit the resulting
|
|
diff.
|
|
|
|
## Reserved namespaces — never mint or hand-write into
|
|
|
|
A **reserved namespace** is an id space no system profile may declare, because an external
|
|
catalog or a device protocol owns the values. None of them has an owning vendor: there is no
|
|
bundle — not even the one whose printers use the catalog — that may write one into a profile.
|
|
|
|
| Space | Status | Rule |
|
|
| --- | --- | --- |
|
|
| `GF*` | Bambu AMS/RFID catalog | declarable by **nobody**, BBL included: Bambu's own ids live in the generated catalog map, never in a profile |
|
|
| `QD_*` | Qidi device protocol | declarable by **nobody**, Qidi included: the box composes these ids at runtime and they are not preset ids |
|
|
| `P` + 7 hex chars (case-insensitive), `"null"` | user-created custom filaments (`CreatePresetsDialog.cpp`) | never appears in system profiles |
|
|
|
|
The two device namespaces, in detail:
|
|
|
|
- **Bambu (`GF*`).** Bambu's device/RFID/cloud catalog is external and opaque, which is a
|
|
reason to keep it out of the profiles rather than to let one bundle own it. Every BBL filament
|
|
mints an `OF` id from its triple like every other vendor's, and the correspondence to Bambu's
|
|
catalog ids lives in one generated file the app applies at the printer boundary — the next
|
|
section. Nothing under `resources/profiles/**` carries a `GF*` id today and nothing can be
|
|
exempted, so a `GF*` id appearing anywhere in the tree is a mistake, whoever wrote it.
|
|
- **Qidi (`QD_*`).** `QD_*` is a device-*protocol* namespace, not a preset id space: the
|
|
Qidi box path composes `QD_<series>_<vendor>_<typeidx>` ids at runtime (slot vendor and
|
|
type indices reported by the device, the series digit inferred client-side from the printer
|
|
model/name). Qidi presets carry ordinary minted `OF*` ids (generics share the OFL ids), so
|
|
a composed id matches no preset and the slot falls back to filament type; translating it to
|
|
the filament's id belongs in `QidiPrinterAgent`. The alternative — treating per-series
|
|
protocol ids as preset ids — would put one product under five ids (`QIDI PLA Rapido` would
|
|
be `QD_0_1_1` through `QD_4_1_1`), exactly the fragmentation the mint rule removes.
|
|
|
|
## The Bambu catalog map
|
|
|
|
Bambu's printers, its AMS and its cloud know only Bambu's own catalog ids. Our profiles carry
|
|
minted `OF` ids like every other vendor's, so one generated file records the correspondence and
|
|
the app applies it **only where an id crosses to or from a Bambu printer**.
|
|
|
|
**The file** is `resources/printers/bambu_filament_ids.json` — a header plus one row per
|
|
catalogued product, keyed by our id:
|
|
|
|
```json
|
|
{
|
|
"source": "https://github.com/bambulab/BambuStudio",
|
|
"bambustudio_commit": "66e405477",
|
|
"generated": "2026-09-04",
|
|
"filaments": {
|
|
"OFhuaUQB": { "bambu_id": "GFB00", "vendor": "Bambu Lab", "type": "ABS", "name": "Bambu ABS" }
|
|
}
|
|
}
|
|
```
|
|
|
|
It ships in `resources/printers/`, next to `filaments_blacklist.json` — deliberately not in
|
|
`resources/profiles/`, where the loader reads every top-level `.json` as a vendor index. It
|
|
holds 100 rows today, one per product BambuStudio ships, and the correspondence is
|
|
one-to-one in both directions.
|
|
|
|
**It is generated, never hand-edited.** `python scripts/update_bambu_filament_ids.py` rebuilds
|
|
it from **BambuStudio's own shipped BBL bundle** — a sparse shallow clone of upstream `master`,
|
|
or `--bambustudio-dir <a BambuStudio resources/profiles checkout>`. Our BBL bundle is a fork of
|
|
Bambu's, tuned and extended independently, so it is not the source of truth for Bambu's ids.
|
|
A row's key is whatever id our tree already mints for that same
|
|
`(filament_vendor, filament_type, filament name)` triple; a product we do not ship gets a freshly
|
|
generated key and the row sits inert until some bundle claims that triple — `OFdyfQvU` /
|
|
`GFG03`, "Bambu PETG Matte", is such a row today.
|
|
|
|
**Regenerate it in the same commit as every BBL profile sync**, and read the drift report it
|
|
prints. Two lines, both informational, neither blocking the write:
|
|
|
|
```text
|
|
upstream ships 'Bambu PETG Matte' (Bambu Lab/PETG), we ship nothing with that identity
|
|
Orca BBL filaments with no row: 135 Orca-only product(s)
|
|
```
|
|
|
|
The first names each upstream product our BBL bundle has no same-identity filament for —
|
|
sometimes a genuinely missing product, sometimes a name drift a follow-up rename would
|
|
converge. The second counts our own BBL filaments that matched no row: 135 of 234 today, of
|
|
which 109 send an `OF` id on the wire and 26 already rode `OF` ids inherited from the
|
|
OrcaFilamentLibrary. **135 is the number to expect at every regeneration** — 109 was the
|
|
one-off size of the transition and stopped being computable from the tree once the BBL bundle
|
|
was re-minted, so do not "fix" the report to print it.
|
|
|
|
**Check 6** lives in `check_filament_ids`, so profile CI runs it alongside the other five. It
|
|
holds the file to its contract: it parses, carries `source` / `bambustudio_commit` /
|
|
`generated`, keys only `OF`-format ids, maps each Bambu id at most once, and — for every row
|
|
whose key the tree actually claims — agrees with the tree on that id's `(vendor, type, name)`
|
|
triple. A row for a product we do not ship is skipped, not an error. The remedy it prints is
|
|
always the same: regenerate the map and commit the diff for review.
|
|
|
|
### The runtime rule: swap on hit
|
|
|
|
Outbound, our id with a row becomes Bambu's; inbound, Bambu's id with a row becomes ours.
|
|
Everything else is forwarded untouched — an `OF` id with no row, a Bambu id for a product we do
|
|
not ship, a `P`-hex user id, `"null"`, an empty string. Translation is confined to the
|
|
boundary: nothing between the boundaries ever holds a Bambu id.
|
|
|
|
Translating one value is a capability of the printer agent: `IPrinterAgent` declares
|
|
`to_orca_filament_id` and `from_orca_filament_id` returning their argument, and `BBLPrinterAgent`
|
|
overrides them with Bambu's map, so an agent whose printers already speak our ids inherits the
|
|
identity default and translates nothing. `NetworkAgent` forwards both to the live agent, so the
|
|
comparison sites below reach them through `wxGetApp().getAgent()` and leave an id untranslated
|
|
while no agent is live. Whole documents are Bambu's business alone:
|
|
`BBLPrinterAgent::to_orca_payload` and `from_orca_payload` rewrite every string under
|
|
`tray_info_idx`, `filament_id` or `filamentId` at any depth; text that does not parse, or carries
|
|
none of those keys, comes back unchanged. The map is loaded once, lazily; a missing or malformed
|
|
file degrades to identity with a log line rather than failing.
|
|
|
|
| Boundary | Where it translates |
|
|
| --- | --- |
|
|
| Everything the agent sends | `BBLPrinterAgent::send_message` and `send_message_to_printer`, plus `PrintParams::ams_mapping_info` in `dispatch_start` — the funnel all five `start_*` calls share |
|
|
| Everything the agent receives | `set_on_message_fn` and `set_on_local_message_fn` wrap their callback, so `MachineObject::parse_json` and everything downstream see our ids only |
|
|
| 3mf export | `Plater::export_3mf` writes Bambu's ids into `slice_info.config`, gated on `preset_bundle.is_bbl_vendor()` — the printer reads that file and knows only its own catalog, and no other vendor's export is affected. The CLI has its own writer in `OrcaSlicer.cpp`; it does the same, gated on the `printer_model` prefix that already decides `Print::is_BBL_printer()` for that run |
|
|
| Project ingest | `Plater::priv::load_files` reverse-maps the project's `filament_ids` before the bundle ingests them, so a project saved by an older Orca or by BambuStudio still resolves the same presets |
|
|
| Prints from the printer's SD card | `SelectMachineDialog::update_print_required_data` reverse-maps each plate's slice-info ids as it adopts the plates, so the AMS mapping dialog pairs them with trays |
|
|
| Bambu-specific comparisons | `CalibUtils.cpp`, `DeviceManager.cpp`, `DeviceCore/DevFilaSystem.cpp`, `DeviceCore/DevFilaBlackList.cpp`, `SelectMachine.cpp`, `AMSDryControl.cpp`, `AMSMaterialsSetting.cpp`, `PresetComboBoxes.cpp`, `ColorDecomposeSupport.cpp` |
|
|
|
|
That last row is the rule to follow when a new Bambu-specific behaviour is added: **translate
|
|
the value you are about to compare, never the table you compare it against.** The shipped data
|
|
those sites read is Bambu's and stays verbatim — `white_fila_ids` in
|
|
`resources/printers/filaments_blacklist.json`, the calibration id lists in
|
|
`resources/printers/<model>.json`, `fila_id` in
|
|
`resources/profiles/BBL/filament/filaments_color_codes.json`.
|
|
|
|
`tests/slic3rutils/test_bambu_filament_ids.cpp` covers the lookups, the payload rewrite and the
|
|
Bambu-specific rules. `orcaslicer_discover_tests` registers a Catch2 tag as a CTest **label**,
|
|
not as part of the test name, so `-R` matches nothing here and the filter is `-L`:
|
|
|
|
```bash
|
|
ctest --test-dir <build dir>/tests/slic3rutils -L BambuFilamentIds
|
|
```
|
|
|
|
### Three places the map deliberately does not reach
|
|
|
|
The map and its lookups live in the GUI library, which libslic3r cannot link against and which a
|
|
GUI-less build does not link at all. Three consequences are known and documented; none is worth
|
|
pulling the map down into libslic3r for.
|
|
|
|
- **The support display type in `PrintConfig.cpp`.** `DynamicPrintConfig::get_filament_type`
|
|
picks `PLA-S` / `Sup.PLA` and `PA-S` / `Sup.PA` for a support filament by testing
|
|
`filament_id` against `GFS00` and `GFS01`, and otherwise falls back on `filament_type` — a
|
|
fallback that returns those same two pairs for `"PLA"` and `"PA"`. Bambu Support W inherits
|
|
`fdm_filament_pla` and Bambu Support G inherits `fdm_filament_pa`, so with their `OF` ids the
|
|
fallback produces exactly what the id branches produced. (The only config that ever carries a
|
|
singular `filament_id` key is the AMS tray config built in `Plater.cpp`, and that one never
|
|
reaches this function.) These two lines are the only mention of a Bambu id anywhere in
|
|
libslic3r, and they need no change.
|
|
- **Config imports.** `PresetBundle::import_presets` (File ▸ Import ▸ Import Configs, for
|
|
`.json` / `.zip` / `.orca_filament` / `.orca_printer` / `.orca_bundle`) and
|
|
`PresetBundle::load_config_file` (the CLI's `--load-settings` of a G-code file with an
|
|
embedded config) both parse inside libslic3r, out of the GUI's reach, so a Bambu id carried
|
|
in such a file lands on the imported preset untranslated. The effect is bounded: that preset
|
|
does not auto-match an AMS tray while the stale id is live, and the id does not survive
|
|
being saved — `Preset::save` writes a `filament_id` key only for a preset whose `inherits` is
|
|
empty, and on the next load an inheriting preset takes its parent's id. A known gap, and not
|
|
a regression: nothing forwarded a stale id before either.
|
|
- **A build configured without the GUI.** `target_link_libraries(OrcaSlicer libslic3r_gui)` sits
|
|
inside `if (SLIC3R_GUI)` in `src/CMakeLists.txt`, so the lookups are not linkable when the GUI
|
|
is off. The CLI's 3mf writer in `src/OrcaSlicer.cpp` therefore guards its translation with
|
|
`#ifdef SLIC3R_GUI`, and a 3mf that such a build slices for a Bambu printer carries our `OF`
|
|
ids in `slice_info.config` rather than Bambu's. Every shipped build enables the GUI, so this
|
|
reaches only a purpose-built GUI-less binary.
|
|
|
|
One more thing worth recording before it is rediscovered:
|
|
`SyncAmsInfoDialog::update_print_required_data` is a structural twin of the SD-card function
|
|
above and carries no translation. It has no callers today and its plate list is only ever read
|
|
for `printer_model_id`, so it is not a live gap — but wiring it up without adding the reverse
|
|
map would silently reproduce the bug.
|
|
|
|
## How CI enforces this
|
|
|
|
Profile CI (`check_profiles.yml`) runs `check_filament_ids()` tree-wide via
|
|
`scripts/orca_extra_profile_check.py`. Its ground truth is
|
|
**`scripts/filament_id_snapshot.json` — the sanctioned state**: the id state derived from the
|
|
tree must equal the snapshot exactly, in both directions. Any change to the id landscape
|
|
therefore surfaces as a diff to that file, and **that snapshot diff is what maintainers review
|
|
and gate in a PR**. Never edit the snapshot by hand — `--update-snapshot` regenerates it
|
|
deterministically (running it twice changes nothing). The snapshot holds one map, `ids`: each
|
|
entry is the product the id is minted from (`filament_vendor`, `filament_type`, `name`) and the
|
|
`filaments` claiming it (`Vendor/Filament`), and it sanctions *state*, never exceptions: no check
|
|
consults it to excuse a preset from a rule, and there is no grandfather list of any kind.
|
|
|
|
The checks, in brief:
|
|
|
|
- **Format** — every id occurring in the tree is `OF` + 6 base62 chars. No exceptions: not a
|
|
snapshot entry, not BBL.
|
|
- **Snapshot equality** — tree claims == snapshot claims **and** each id's declared triple ==
|
|
its snapshot entry, both directions: any `filament_vendor`/`filament_type`/name change
|
|
surfaces as a snapshot diff.
|
|
- **Identity** — the id is a function of the triple alone. A declared `OF*` id must equal the
|
|
mint (or a low salt iteration) of its declarer's own triple; the id an instantiated preset
|
|
*inherits* must equal the mint of *its* own triple, however it inherits it (a root, a real
|
|
filament, a library preset — structure rule 1); and every instantiated system filament must
|
|
resolve an effective id at all (recall: an id-less one is a hard load error in C++ that
|
|
discards the whole vendor bundle). The errors print the expected id.
|
|
- **Reserved namespaces** — `GF*`, `QD_*`, `P<7-hex>` or `"null"` claimed by any vendor,
|
|
BBL and Qidi included.
|
|
- **Triple integrity** — every declarer must resolve a non-empty `filament_vendor` and
|
|
`filament_type` (generics use `"Generic"`), and all declarers of one filament within a
|
|
bundle must agree on the triple.
|
|
- **Bambu catalog map** — `resources/printers/bambu_filament_ids.json` parses, carries its
|
|
`source` / `bambustudio_commit` / `generated` header, keys only `OF`-format ids, maps each
|
|
Bambu id at most once, and agrees with the tree on the triple of every row whose key the
|
|
tree claims. See [The Bambu catalog map](#the-bambu-catalog-map); the remedy is always to
|
|
regenerate, never to hand-edit.
|
|
|
|
A profile that declares a **reserved-namespace** id — `GF*`, `QD_*` or `P<7-hex>`, whatever
|
|
its vendor — cannot pass the format check, so `--update-snapshot` refuses to sanction it
|
|
rather than hide the mistake until CI. For a Bambu-cataloged product, the catalog map is where
|
|
the correspondence belongs. Any other new sharing via a *declared* id is caught by the identity
|
|
check; sharing through inheritance carries no declaration to check and surfaces only as a new
|
|
claim in the snapshot diff — which is exactly why that diff is the gate.
|
|
|
|
`orca_extra_profile_check.py` separately holds every declared id to the AMS 8-character limit,
|
|
tree-wide and for every vendor alike, scoped to the presets a vendor's index actually
|
|
references (a file the index never loads cannot break AMS matching).
|
|
|
|
Complementing the Python checks, CI also runs the C++ profile validator with `-f`
|
|
(`check_filament_subtypes`): it loads the bundle exactly as the app does and flags any printer
|
|
for which two or more compatible filament presets share one `filament_id` — the runtime-shaped
|
|
ambiguity check behind structure rule 3.
|
|
|
|
## FAQ
|
|
|
|
- **A new color of an existing product?** Never a new id — colors are not filaments.
|
|
- **A second diameter (1.75 mm and 2.85 mm) of the same product?** A sibling filament with its
|
|
own id: two diameters are separately selectable spool products.
|
|
- **A high-speed tune of an existing material for another printer model?** Same filament:
|
|
keep the base name and inherit its root; no id key needed.
|
|
- **A tuned generic ("our profile for Generic PLA")?** Inherit `Generic PLA @System`, keep the
|
|
`Generic PLA` base name, set `compatible_printers`; no id key needed.
|
|
- **A branded filament that borrows a generic's settings?** Fine — inherit `Generic X @System`
|
|
(or any real filament) for the settings and declare the id of your own filament; run
|
|
`python scripts/orca_id_tool.py --generate` to mint it. Inheritance never changes the id.
|
|
- **I need to fix a filament's `filament_vendor` or `filament_type`.** Fix the config, run
|
|
`--generate --vendor <Vendor>` (preview with `--dry-run`), then `--update-snapshot`, and commit
|
|
the profile and snapshot diffs together. The id re-derives from the corrected identity, and
|
|
nothing forwards the old value, so a tray or record still holding it falls back to matching by
|
|
filament type.
|
|
- **I need to rename a filament.** Rename the presets (adding `renamed_from`, which keeps the
|
|
preset *name* resolving), then `--generate --vendor <Vendor>` (preview with `--dry-run`), then
|
|
`--update-snapshot`. The id follows the new filament name; as with any identity fix, the old id
|
|
is not forwarded.
|
|
- **Can I reuse a `QD_*` id for a Qidi profile?** No — nobody can. It is the device protocol's
|
|
own id space: the box composes those values at runtime and no preset carries one. Author
|
|
Qidi filaments like any other vendor's.
|
|
- **CI says my filament needs an id.** Run `python scripts/orca_id_tool.py --generate`, then
|
|
`--update-snapshot`, and commit both diffs. Do not type an id by hand.
|
|
|
|
For general profile authoring, see the profile development guide on the
|
|
[OrcaSlicer wiki](https://www.orcaslicer.com/wiki).
|