mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
Stop requiring a filament id snapshot update when filaments change
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: orca-profiles
|
||||
description: Use when creating, modifying, reviewing or debugging OrcaSlicer FFF system profiles under resources/profiles, including printer/vendor/nozzle/material additions, bundle indexes and versions, preset renames, setting_id, filament_id and filament_id_snapshot.json. Also use for missing presets or vendors, ignored profile settings, ambiguous AMS filament matches, and failures from orca_profile_tool.py, check_profile.sh/.bat, OrcaSlicer_profile_validator or the Check profiles CI job.
|
||||
description: Use when creating, modifying, reviewing or debugging OrcaSlicer FFF system profiles under resources/profiles, including printer/vendor/nozzle/material additions, bundle indexes and versions, preset renames, setting_id and filament_id. Also use for missing presets or vendors, ignored profile settings, ambiguous AMS filament matches, and failures from orca_profile_tool.py, check_profile.sh/.bat, OrcaSlicer_profile_validator or the Check profiles CI job.
|
||||
---
|
||||
|
||||
# OrcaSlicer system profiles
|
||||
@@ -21,7 +21,7 @@ Paths below are relative to this skill. Commands run from the repository root.
|
||||
| Add a printer or nozzle; change models, variants, assets or extruder vectors | [machine-profiles.md](references/machine-profiles.md) |
|
||||
| Add a quality tier or tune a process | [process-profiles.md](references/process-profiles.md) |
|
||||
| Create a vendor bundle; diagnose loading or inheritance; migrate preset names | [vendor-bundle.md](references/vendor-bundle.md) |
|
||||
| Change ids or snapshot claims; diagnose AMS identity | [ids.md](references/ids.md), then `docs/HLSD/filament_id.md` for identity changes |
|
||||
| Change ids; diagnose AMS identity | [ids.md](references/ids.md), then `docs/HLSD/filament_id.md` for identity changes |
|
||||
| Review a profile diff | [review-checklist.md](references/review-checklist.md) |
|
||||
| Run checks, interpret failures, test another tree or verify in the app | [validation.md](references/validation.md) |
|
||||
|
||||
@@ -71,14 +71,11 @@ Paths below are relative to this skill. Commands run from the repository root.
|
||||
python3 scripts/orca_profile_tool.py normalize --vendor "<Vendor>"
|
||||
python3 scripts/orca_profile_tool.py update-index --vendor "<Vendor>"
|
||||
python3 scripts/orca_profile_tool.py generate-id --vendor "<Vendor>"
|
||||
python3 scripts/orca_profile_tool.py update-snapshot
|
||||
python3 scripts/orca_profile_tool.py check
|
||||
```
|
||||
|
||||
Writing commands support `--dry-run`. Inspect their diffs: `normalize` changes content and can
|
||||
reformat entire files. `update-snapshot` is tree-wide; include its diff whenever a filament id
|
||||
**or claim** changes, even if no new id was minted. Skip it when filament identity and claims
|
||||
are unchanged. Stop and resolve command errors before proceeding.
|
||||
reformat entire files. Stop and resolve command errors before proceeding.
|
||||
|
||||
**Do not use `trim` in this workflow:** it can delete newly authored, unindexed profiles.
|
||||
Do not use `normalize --force` for routine edits.
|
||||
|
||||
@@ -6,7 +6,7 @@ Orca-generated ids are deterministic hashes of identity. **Never invent an id or
|
||||
[BBL's authoritative setting ids](#bbls-exception-precisely).
|
||||
|
||||
`docs/HLSD/filament_id.md` is the authoritative design document for `filament_id` — the id landscape, the
|
||||
snapshot as the maintainer gate, and the Bambu catalog map. This page is the tooling half.
|
||||
checks CI runs, and the Bambu catalog map. This page is the tooling half.
|
||||
|
||||
| | `setting_id` | `filament_id` |
|
||||
| --- | --- | --- |
|
||||
@@ -32,12 +32,10 @@ Use `scripts/orca_profile_tool.py` with a subcommand:
|
||||
| `normalize` | rewrites profile files into their canonical shape |
|
||||
| `trim` | deletes profile files no `<vendor>.json` list references |
|
||||
| `update-index` | rebuilds the `*_list` sections from the files on disk |
|
||||
| `update-snapshot` | re-records `scripts/filament_id_snapshot.json` |
|
||||
|
||||
The order after adding, renaming or deleting files — each step feeds the next, so it is not
|
||||
interchangeable — is `normalize` → `update-index` → `generate-id` → `update-snapshot` → `check`.
|
||||
The [authoring workflow](../SKILL.md#creating-or-modifying-a-profile) has the commands;
|
||||
`update-snapshot` is needed when filament ids or claims change.
|
||||
interchangeable — is `normalize` → `update-index` → `generate-id` → `check`.
|
||||
The [authoring workflow](../SKILL.md#creating-or-modifying-a-profile) has the commands.
|
||||
|
||||
> **`trim` deletes.** It removes every profile file the index does not list — including the one you just
|
||||
> added and have not registered yet. Register first, or skip `trim` entirely; it is a cleanup sweep, not
|
||||
@@ -48,15 +46,15 @@ filesystem (the `setting_id` pass walks the filesystem, so a bundle whose index
|
||||
still assignable). A new filament file is therefore invisible to `generate-id`'s filament_id pass until
|
||||
it is registered — its `setting_id` is written regardless.
|
||||
|
||||
- `--dry-run` works on every writing command (`generate-id`, `normalize`, `trim`, `update-index`,
|
||||
`update-snapshot`) and writes nothing.
|
||||
- `--dry-run` works on every writing command (`generate-id`, `normalize`, `trim`, `update-index`)
|
||||
and writes nothing.
|
||||
- `--filament-id` / `--setting-id` narrow `generate-id`; they exclude each other, and passing neither
|
||||
writes both.
|
||||
- `--vendor` is repeatable and narrows **only what is written** — the id is a function of the triple
|
||||
alone, so a narrowed run writes exactly what a full run would. An unknown vendor exits 1 before any
|
||||
write. `--vendor` on `check` narrows the per-vendor checks only; the `setting_id` and `filament_id`
|
||||
passes stay tree-wide. `update-snapshot` takes no `--vendor` at all.
|
||||
- `--profiles DIR` points any command at another tree — with the `--snapshot` companion rule, see
|
||||
passes stay tree-wide.
|
||||
- `--profiles DIR` points any command at another tree — see
|
||||
[Checking a copy of the tree](validation.md#checking-a-copy-of-the-tree).
|
||||
- `--profile-type` narrows `normalize`, `trim` and `update-index` to `machine_model`, `process`,
|
||||
`filament` or `machine`.
|
||||
@@ -71,8 +69,8 @@ whole files into canonical shape — which is why `check` demands it already be
|
||||
CRLF committed (OrcaFilamentLibrary, Anycubic and RH3D among them), so a `normalize` pass there rewrites
|
||||
every line — read the diff before committing it.
|
||||
|
||||
On a clean tree `check`, `generate-id --dry-run` and `update-snapshot --dry-run` all exit 0 with zero
|
||||
findings. That is the baseline to restore before opening a PR.
|
||||
On a clean tree `check` and `generate-id --dry-run` both exit 0 with zero findings. That is the
|
||||
baseline to restore before opening a PR.
|
||||
|
||||
## What `generate-id` does and does not fix
|
||||
|
||||
@@ -88,7 +86,7 @@ Refuses to write (reports only): a base62 collision between two products, an emp
|
||||
`filament_type`, a broken `inherits` chain, roots of one filament resolving divergent `(vendor, type)`
|
||||
pairs.
|
||||
|
||||
**Does not fix: a preset that *inherits* a wrong `filament_id`.** This is check 3b, and it is the trap
|
||||
**Does not fix: a preset that *inherits* a wrong `filament_id`.** This is check 2b, and it is the trap
|
||||
most likely to bite. It happens when a branded filament inherits a generic for its settings:
|
||||
|
||||
```jsonc
|
||||
@@ -107,8 +105,8 @@ Two fixes, in order of preference:
|
||||
2. **Declare the tool-computed key on the preset itself.** Use the expected value reported by `check`
|
||||
or compute it with the function below; this is not a manually chosen id. Make sure the preset
|
||||
resolves the right `filament_vendor` and `filament_type` first — with
|
||||
neither set, the triple resolves through the generic parent and the branded product is minted, and
|
||||
then sanctioned in the snapshot, under vendor `Generic`. If you need the id before the file exists:
|
||||
neither set, the triple resolves through the generic parent and the branded product is minted
|
||||
under vendor `Generic`. If you need the id before the file exists:
|
||||
|
||||
```bash
|
||||
python3 -c "import sys; sys.path.insert(0,'scripts'); from orca_profile_tool import generate_filament_id as g; print(g('Polymaker','PLA','PolyLite PLA'))"
|
||||
@@ -119,38 +117,6 @@ Two fixes, in order of preference:
|
||||
|
||||
The `setting_id` equivalent is `generate_preset_setting_id('<vendor folder>', '<type>', '<name>')`.
|
||||
|
||||
## The snapshot
|
||||
|
||||
`scripts/filament_id_snapshot.json` is the sanctioned state: the id landscape derived from the tree must
|
||||
equal it exactly, in both directions. **Any change to a filament id or its claims must be committed with
|
||||
the profiles.**
|
||||
|
||||
To trace an id from an error, search for it in the snapshot. Each entry records its identity triple
|
||||
and `<vendor folder>/<name-before-@>` claims, one per bundle/product pair rather than per preset.
|
||||
|
||||
```bash
|
||||
python3 scripts/orca_profile_tool.py update-snapshot
|
||||
```
|
||||
|
||||
Never hand-edit it. It is regenerated deterministically (1-space indent, LF, id-sorted) and
|
||||
refuses to write two states it could not record truthfully: a tree it could not read whole, and an id
|
||||
declared under more than one triple. It does **not** judge the ids themselves — it records state, `check`
|
||||
judges it, so a bad id lands in the diff and fails there instead. `generate-id` never touches the
|
||||
snapshot, and reminds you with a warning **only when it actually wrote a `filament_id`** — not on a
|
||||
`--dry-run`, and not when only `setting_id`s changed.
|
||||
|
||||
Reviewing a snapshot diff:
|
||||
|
||||
| Diff | Means |
|
||||
| --- | --- |
|
||||
| new id + new claim | a genuinely new product — confirm it is not a rename in disguise |
|
||||
| id removed | a product left the tree, or its identity changed — the old id is not forwarded anywhere |
|
||||
| triple changed under an existing id | `filament_vendor`/`filament_type`/name was edited; deliberate? |
|
||||
| claim added/removed only | a bundle started or stopped shipping that product |
|
||||
|
||||
An entry with an empty `filaments` list is legitimate — declared, but not yet claimed by an instantiated
|
||||
preset.
|
||||
|
||||
## BBL's exception, precisely
|
||||
|
||||
`RESERVED_VENDORS = {"BBL"}` covers **`setting_id` assignment only**, keyed on the *folder* name:
|
||||
@@ -159,8 +125,8 @@ preset.
|
||||
`setting_id` therefore **cannot be fixed by the tool**, yet the presence rule still applies to it —
|
||||
carry over Bambu's authoritative id by hand.
|
||||
- BBL is not exempt from anything else: bases still get their `setting_id` stripped, ids must still be
|
||||
globally unique, and BBL `filament_id`s are minted like everyone else's — every id the snapshot records
|
||||
as claimed by BBL is an `OF*`.
|
||||
globally unique, and BBL `filament_id`s are minted like everyone else's — every one of them is an
|
||||
`OF*`.
|
||||
|
||||
## Ids other systems compose
|
||||
|
||||
|
||||
@@ -51,11 +51,11 @@ and take the whole vendor bundle down; an unindexed file gets reviewed, merged a
|
||||
## 3. Are ids generated, not written?
|
||||
|
||||
No hand-typed or copied `setting_id` / `filament_id`. Instantiated presets have a `setting_id`; bases do
|
||||
not. A filament id change comes with a `scripts/filament_id_snapshot.json` diff in the same commit.
|
||||
`check` enforces all of that; what it cannot tell you is whether the identity *should* have moved.
|
||||
not. `check` enforces all of that; what it cannot tell you is whether the identity *should* have moved.
|
||||
|
||||
Read the snapshot diff as the identity gate: a removed id or a changed triple means a product's identity
|
||||
moved, and the old id is not forwarded anywhere. Confirm that was intended.
|
||||
A rewritten or removed `filament_id` means a product's identity moved — a rename, or an edited
|
||||
`filament_vendor` / `filament_type` — and the old id is not forwarded anywhere. Confirm that was
|
||||
intended, and that a new id is not a rename in disguise.
|
||||
|
||||
*Why:* a duplicate `filament_id` on one printer makes AMS spool matching a coin toss; a copied
|
||||
`setting_id` breaks preset identity. See [ids.md](ids.md).
|
||||
|
||||
@@ -62,8 +62,7 @@ as SKIP for a vendor with no `machine/` folder.
|
||||
## `orca_profile_tool.py check`
|
||||
|
||||
`check` is one subcommand of the tool that also owns
|
||||
`generate-id`, `normalize`, `trim`, `update-index` and `update-snapshot`; see [ids.md](ids.md) for the
|
||||
writing half.
|
||||
`generate-id`, `normalize`, `trim` and `update-index`; see [ids.md](ids.md) for the writing half.
|
||||
|
||||
| Per vendor | Catches |
|
||||
| --- | --- |
|
||||
@@ -177,21 +176,14 @@ pre-existing user files may be present.
|
||||
|
||||
## Checking a copy of the tree
|
||||
|
||||
Use `--profiles DIR` on the Python tool and `-p DIR` on the validator.
|
||||
`check` and `update-snapshot` describe a tree's sanctioned id state, so pointing them elsewhere also
|
||||
needs `--snapshot PATH` for that tree — passing `--profiles` without it exits 2 rather than silently
|
||||
judging the copy against `resources/profiles`'s snapshot.
|
||||
|
||||
**The wrappers' `--profiles` / `-ProfilesDir` redirects only their validator checks.** Their
|
||||
`profile_tool` check still reads this checkout's `resources/profiles`. To validate a copy fully,
|
||||
run the Python check separately with that tree's snapshot, then name only validator checks:
|
||||
Use `--profiles DIR` on the Python tool and `-p DIR` on the validator. The wrappers' `--profiles` /
|
||||
`-ProfilesDir` passes the tree to both, so one run validates a copy fully:
|
||||
|
||||
```bash
|
||||
python3 scripts/orca_profile_tool.py check --profiles "<tree>" --snapshot "<snapshot.json>"
|
||||
./scripts/check_profile.sh --profiles "<tree>" validate_system validate_slice validate_filament_subtypes validate_custom
|
||||
./scripts/check_profile.sh --profiles "<tree>"
|
||||
```
|
||||
|
||||
On Windows use `py -3` and `scripts\check_profile.bat -ProfilesDir "<tree>"` with the same check names.
|
||||
On Windows use `scripts\check_profile.bat -ProfilesDir "<tree>"`.
|
||||
|
||||
## Testing in the app
|
||||
|
||||
@@ -238,7 +230,6 @@ Keep stems tidy too, but a space immediately before `.json` is not a trailing pa
|
||||
| `[ERROR] … normalize would <change>` / `<V>.json: update-index would rebuild <lists>` | run that command and commit the result |
|
||||
| `[ERROR] <V> has N <type> profiles named "<name>"` | identify the intended preset and remove or rename the duplicate; use `trim --dry-run` only for deliberate unindexed-file cleanup |
|
||||
| `[ERROR] … must not have a setting_id` / `is missing a setting_id` | `generate-id --setting-id` |
|
||||
| `[ERROR] filament_id "<id>" is not sanctioned by …snapshot.json` | `update-snapshot`, commit the diff |
|
||||
| `inherits filament_id "X" but its own triple … mints "Y"` | `generate-id` will **not** fix this — see [ids.md](ids.md) |
|
||||
| `vendor <V>'s config version: <s> invalid` | the `version` string is not Semver-parseable |
|
||||
| `[json.exception.type_error.302] type must be string` | locate the non-string value in the index or model; see [failure scopes](vendor-bundle.md#failure-modes-ranked-by-blast-radius) |
|
||||
@@ -260,5 +251,5 @@ once the run is green.
|
||||
|
||||
The job name is also the required check for the delegated-merge bot, which lets a vendor maintainer
|
||||
self-merge a `resources/profiles/<Their vendor>/` PR with no human review — so whatever CI does not check
|
||||
is what ships unreviewed. Its denied patterns refuse `^scripts/` and any `.py`, so a PR that must update
|
||||
`scripts/filament_id_snapshot.json` always needs a maintainer.
|
||||
is what ships unreviewed. Its denied patterns refuse `^scripts/` and any `.py`, so a PR that touches the
|
||||
tooling always needs a maintainer.
|
||||
|
||||
@@ -171,5 +171,5 @@ A separate tree (`Template.json` + `Template/`) holding filament and process tem
|
||||
scaffold for shipped profiles — `CreatePresetsDialog.cpp` reads it for the in-app "create a custom
|
||||
printer/filament" wizard, so editing it changes what users get when they create a custom preset.
|
||||
`check_profile.sh`'s validator checks default to `resources/profiles` (redirectable with `-p`), and so
|
||||
does `orca_profile_tool.py` (redirectable with `--profiles`, plus `--snapshot` for the id checks);
|
||||
does `orca_profile_tool.py` (redirectable with `--profiles`);
|
||||
neither covers this tree.
|
||||
|
||||
@@ -80,8 +80,8 @@ jobs:
|
||||
set +e
|
||||
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -s -l 2 2>&1 | tee ${{ runner.temp }}/validate_slice.log
|
||||
exit ${PIPESTATUS[0]}
|
||||
# All vendors' filament_id collisions were fixed (see scripts/filament_id_snapshot.json),
|
||||
# so the duplicate-filament-subtype check runs tree-wide.
|
||||
# All vendors' filament_id collisions were fixed, so the duplicate-filament-subtype
|
||||
# check runs tree-wide.
|
||||
- name: validate filament subtype check
|
||||
id: validate_filament_subtypes
|
||||
continue-on-error: true
|
||||
|
||||
+31
-51
@@ -36,21 +36,17 @@ This page is the rule for authoring `filament_id` in system profiles
|
||||
> **Never write a `filament_id` value by hand.** A new filament gets its id from
|
||||
> `python scripts/orca_profile_tool.py generate-id`; one already in the tree has one — inherit it.
|
||||
|
||||
## The design, in two pieces
|
||||
## The design
|
||||
|
||||
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.
|
||||
the system is built to make them impossible: 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.
|
||||
CI holds every id in the tree to that rule, so the profiles themselves are the whole record of
|
||||
which products exist and which bundles ship them.
|
||||
|
||||
## Who consumes the id
|
||||
|
||||
@@ -160,8 +156,8 @@ key needed). Tuning a generic material → **join the OrcaFilamentLibrary filame
|
||||
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-id` (preview with `--dry-run`, confine with `--vendor`)
|
||||
and gated by the `update-snapshot` diff; the exact sequence is in the FAQ. Nothing forwards
|
||||
**by design**, applied by `generate-id` (preview with `--dry-run`, confine with `--vendor`);
|
||||
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.
|
||||
@@ -196,8 +192,8 @@ Snapmaker bundles alike; the OFL generic `Generic/PLA/Generic PLA` mints `OFDSrz
|
||||
by 35 bundles — most by independent declarations converging on the same mint, the rest
|
||||
purely through inheritance from the OFL preset.
|
||||
|
||||
Nothing but the triple feeds the mint — not the rest of the tree, not the snapshot, not what
|
||||
another preset of the product happens to carry. Determined triple, determined id: one product
|
||||
Nothing but the triple feeds the mint — not the rest of the tree, not what another preset of
|
||||
the product happens to carry. Determined triple, determined id: one product
|
||||
carries one id and there is no second acceptable value for it, so any other value on a preset
|
||||
is a mismatch `check` reports and `generate-id` pulls back. Two *different* products whose
|
||||
triples mint the same base62 value would be a collision (a roughly 36-bit id space against a
|
||||
@@ -214,17 +210,15 @@ Workflow for a new filament:
|
||||
# 1. Author the filament with NO filament_id key anywhere.
|
||||
python scripts/orca_profile_tool.py generate-id --dry-run # 2. preview the ids — writes nothing
|
||||
python scripts/orca_profile_tool.py generate-id # 3. apply them to the profile file(s)
|
||||
python scripts/orca_profile_tool.py update-snapshot # 4. record the new claims in the snapshot
|
||||
python scripts/orca_profile_tool.py check # 5. validate — everything CI checks
|
||||
# 6. Commit the profile edits together with scripts/filament_id_snapshot.json, for review.
|
||||
python scripts/orca_profile_tool.py check # 4. validate — everything CI checks
|
||||
```
|
||||
|
||||
`generate-id` 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
|
||||
mismatching id is the one case left to the author — check 2b names it, and the fix is to inherit
|
||||
a preset of the same filament or to give the preset its own key. A declaration is left alone
|
||||
exactly when it already equals the one id its triple mints, and a collision (check 3d) is
|
||||
exactly when it already equals the one id its triple mints, and a collision (check 2d) is
|
||||
reported and left unwritten. 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
|
||||
@@ -242,9 +236,7 @@ loudly), and a no-op on a tree that already passes `check`.
|
||||
- `--dry-run` reports what the run would do and writes nothing, so
|
||||
`generate-id --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.
|
||||
`resources/profiles`).
|
||||
|
||||
The tool's other commands maintain the tree around the ids: `fix` normalises profile files,
|
||||
`trim` drops files no `<vendor>.json` list references, and `update-index` rebuilds those lists.
|
||||
@@ -252,13 +244,11 @@ They do not touch ids; `--help` documents them.
|
||||
|
||||
**Identity fixes need no separate command.** `generate-id` 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-id` (confine it with
|
||||
`--vendor`, preview it with `--dry-run`), then `update-snapshot` and review the diff.
|
||||
`filament_type` correction is just: fix the config and run `generate-id` (confine it with
|
||||
`--vendor`, preview it with `--dry-run`).
|
||||
|
||||
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_profile_tool.py generate-id`; once the id is minted,
|
||||
the snapshot checks likewise point at `update-snapshot` and tell you to commit the resulting
|
||||
diff.
|
||||
the instruction to run `python scripts/orca_profile_tool.py generate-id`.
|
||||
|
||||
## Ids other systems compose
|
||||
|
||||
@@ -339,7 +329,7 @@ OrcaFilamentLibrary. **135 is the number to expect at every regeneration** — 1
|
||||
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 5** lives in `check_filament_ids`, so profile CI runs it alongside the other four. It
|
||||
**Check 4** lives in `check_filament_ids`, so profile CI runs it alongside the other three. 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)`
|
||||
@@ -428,23 +418,14 @@ map would silently reproduce the bug.
|
||||
## How CI enforces this
|
||||
|
||||
Profile CI (`check_profiles.yml`) runs `check_filament_ids()` tree-wide via
|
||||
`scripts/orca_profile_tool.py check`. 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.
|
||||
`scripts/orca_profile_tool.py check`. Every check judges the tree against the rules on this
|
||||
page and nothing else — there is no recorded id state to match and 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.
|
||||
- **Format** — every id occurring in the tree is `OF` + 6 base62 chars. No exceptions, not
|
||||
even BBL.
|
||||
- **Identity** — the id is a function of the triple alone. A declared `OF*` id must equal the
|
||||
one id its declarer's own triple mints, with no second acceptable value; the id an
|
||||
instantiated preset *inherits* must equal the mint of *its* own triple, however it inherits
|
||||
@@ -463,9 +444,8 @@ The checks, in brief:
|
||||
|
||||
A profile that declares an id no triple mints — a Bambu catalog id, a composed Qidi one, a
|
||||
hand-typed value, whatever its vendor — fails the format check. For a Bambu-cataloged product
|
||||
the catalog map is where the correspondence belongs. 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.
|
||||
the catalog map is where the correspondence belongs. Two products sharing one id are caught by
|
||||
the identity check whether the id is declared or inherited.
|
||||
|
||||
The same `check` run 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
|
||||
@@ -489,19 +469,19 @@ ambiguity check behind structure rule 3.
|
||||
(or any real filament) for the settings and declare the id of your own filament; run
|
||||
`python scripts/orca_profile_tool.py generate-id` 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-id --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
|
||||
`generate-id --vendor <Vendor>` (preview with `--dry-run`), and commit the result. 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-id --vendor <Vendor>` (preview with `--dry-run`), then
|
||||
`update-snapshot`. The id follows the new filament name; as with any identity fix, the old id
|
||||
preset *name* resolving), then `generate-id --vendor <Vendor>` (preview with `--dry-run`). 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 — it is not a mint, so it is not a
|
||||
`filament_id`. Those values are composed by the box 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_profile_tool.py generate-id`, then
|
||||
`update-snapshot`, and commit both diffs. Do not type an id by hand.
|
||||
- **CI says my filament needs an id.** Run `python scripts/orca_profile_tool.py generate-id` and
|
||||
commit the result. 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).
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
under emulation on ARM64.
|
||||
|
||||
.PARAMETER ProfilesDir
|
||||
Profile tree to validate (default: resources\profiles). profile_tool always looks at the
|
||||
tree next to the script, so this only redirects the validator checks.
|
||||
Profile tree to validate (default: resources\profiles).
|
||||
|
||||
.PARAMETER Vendor
|
||||
Check only this vendor, named after its <Vendor>.json (e.g. "Co Print"). validate_custom is
|
||||
@@ -440,7 +439,7 @@ function Expand-VendorPresets([string] $Zip, [string] $Tree, [string] $Prefix) {
|
||||
$CheckBodies = @{
|
||||
|
||||
profile_tool = {
|
||||
Invoke-Tool -Exe (Resolve-Python) -Arguments (@((Join-Path $RepoRoot 'scripts\orca_profile_tool.py'), 'check') + $VendorPyArgs)
|
||||
Invoke-Tool -Exe (Resolve-Python) -Arguments (@((Join-Path $RepoRoot 'scripts\orca_profile_tool.py'), 'check', '--profiles', $ProfilesDir) + $VendorPyArgs)
|
||||
}
|
||||
|
||||
validate_system = {
|
||||
|
||||
@@ -82,9 +82,7 @@ Options:
|
||||
Note: profile_tool is the only check that is not the validator binary; it makes the static
|
||||
checks the validator cannot, because the validator loads the tree the way the slicer does
|
||||
and so never sees a profile no <vendor>.json indexes, a preset name two files claim, or a
|
||||
file normalize and update-index would still rewrite. It always looks at the tree next to
|
||||
the script (<repo>/resources/profiles); --profiles only redirects the validator checks,
|
||||
because validating another tree's ids needs that tree's own filament_id snapshot too.
|
||||
file normalize and update-index would still rewrite.
|
||||
|
||||
Note: --vendor narrows validate_custom too, by keeping only that vendor's presets in each
|
||||
fixture tree. The one check it cannot narrow is validate_slice for a vendor that ships no
|
||||
@@ -366,7 +364,7 @@ resolve_validator() {
|
||||
# ---------------------------------------------------------------------------- checks
|
||||
|
||||
check_profile_tool() {
|
||||
python3 "${REPO_ROOT}/scripts/orca_profile_tool.py" check --vendor "${VENDOR}"
|
||||
python3 "${REPO_ROOT}/scripts/orca_profile_tool.py" check --profiles "${PROFILES_DIR}" --vendor "${VENDOR}"
|
||||
}
|
||||
|
||||
check_validate_system() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+33
-243
@@ -10,23 +10,19 @@ commands:
|
||||
normalize rewrite profile files into their canonical shape
|
||||
trim delete profile files no <vendor>.json list references
|
||||
update-index regenerate the *_list sections of <vendor>.json
|
||||
update-snapshot re-record scripts/filament_id_snapshot.json
|
||||
|
||||
options shared by several commands:
|
||||
--vendor VENDOR act on one vendor bundle only; repeatable, empty means all
|
||||
(every command but update-snapshot)
|
||||
--profile-type TYPE one of machine_model/process/filament/machine; repeatable
|
||||
(normalize, trim, update-index)
|
||||
--dry-run report what would change and write nothing (every command
|
||||
that writes)
|
||||
--profiles DIR act on another profile tree (default: resources/profiles);
|
||||
check and update-snapshot then need --snapshot PATH too,
|
||||
since the snapshot describes resources/profiles alone
|
||||
--profiles DIR act on another profile tree (default: resources/profiles)
|
||||
|
||||
After adding, renaming or deleting profile files, run:
|
||||
normalize -> update-index -> generate-id -> update-snapshot -> check
|
||||
normalize -> update-index -> generate-id -> check
|
||||
normalize supplies missing types; update-index registers presets before id
|
||||
generation. update-snapshot is needed when filament ids or claims change.
|
||||
generation.
|
||||
Use trim only for deliberate cleanup, previewed with --dry-run: it judges against
|
||||
the current index and can delete newly added, unindexed presets.
|
||||
|
||||
@@ -55,8 +51,8 @@ filament_id policy (see docs/HLSD/filament_id.md):
|
||||
filament_id = "OF" + base62_6( uuid5(FILAMENT_ID_NAMESPACE,
|
||||
"filament_product/<filament_vendor>/<filament_type>/<filament_name>") )
|
||||
8 chars total, which satisfies the AMS length limit. Nobody invents ids by
|
||||
hand, and nothing but the triple feeds the mint — not the rest of the tree,
|
||||
not the snapshot. Two products whose triples mint one id (a base62
|
||||
hand, and nothing but the triple feeds the mint — not the rest of the tree.
|
||||
Two products whose triples mint one id (a base62
|
||||
collision; odds ~1e-5 over the whole tree) is an error --check reports and
|
||||
--generate refuses to write; the remedy is a rename so the triples differ,
|
||||
never a salted or hand-picked second id.
|
||||
@@ -70,12 +66,6 @@ filament_id policy (see docs/HLSD/filament_id.md):
|
||||
the app applies at the printer boundary), the QD_* ids a Qidi box composes at
|
||||
runtime, and the P+7-hex ids CreatePresetsDialog.cpp gives user-created
|
||||
filaments all fail the format rule like any other stray value.
|
||||
* scripts/filament_id_snapshot.json is the sanctioned-state snapshot: one
|
||||
entry per id, carrying the product triple it is minted from and the
|
||||
"Vendor/Filament" presets claiming it. It must exactly equal the tree-derived
|
||||
state at all times, so any id/claim/triple change shows up as a reviewable
|
||||
diff to that file (the maintainer gate). It sanctions state, never
|
||||
exceptions: no check consults it to excuse a preset from the rules above.
|
||||
|
||||
setting_id policy (see AGENTS.md "Critical Constraints"):
|
||||
* setting_id is a PRESET id, a pure function of the preset's identity:
|
||||
@@ -123,7 +113,6 @@ FILAMENT_ID_LENGTH = 6 # base62 digits after the "OF" prefix -> 8 chars total
|
||||
|
||||
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PROFILES_DIR = os.path.normpath(os.path.join(SCRIPTS_DIR, "..", "resources", "profiles"))
|
||||
SNAPSHOT_PATH = os.path.join(SCRIPTS_DIR, "filament_id_snapshot.json")
|
||||
# The single source of truth for the map path; update_bambu_filament_ids.py
|
||||
# imports this rather than recomputing it.
|
||||
BAMBU_MAP_PATH = os.path.normpath(
|
||||
@@ -193,7 +182,6 @@ _JSON_STR = r'"(?:[^"\\]|\\.)*"'
|
||||
|
||||
GENERATE_CMD = "python scripts/orca_profile_tool.py generate-id"
|
||||
SETTING_ID_CMD = '"python scripts/orca_profile_tool.py generate-id --setting-id"'
|
||||
UPDATE_HINT = 'run "python scripts/orca_profile_tool.py update-snapshot" and commit the diff for maintainer review'
|
||||
BAMBU_MAP_HINT = 'regenerate the map with "python scripts/update_bambu_filament_ids.py" and commit the diff for maintainer review'
|
||||
NORMALIZE_HINT = 'try "python scripts/orca_profile_tool.py normalize" to fix common issues automatically'
|
||||
|
||||
@@ -246,8 +234,8 @@ def _base62_tail(n, length):
|
||||
"""The low `length` base62 digits of n, most-significant first.
|
||||
|
||||
The shared tail of both id rules. Its output bytes are pinned by the C++
|
||||
golden vectors (tests/libslic3r/test_preset_setting_id.cpp) and by the
|
||||
filament_id snapshot — never change it.
|
||||
golden vectors (tests/libslic3r/test_preset_setting_id.cpp) and by every
|
||||
filament_id in the tree — never change it.
|
||||
"""
|
||||
digits = []
|
||||
for _ in range(length):
|
||||
@@ -483,9 +471,8 @@ def resolve_triple(name, filaments, ofl_filaments):
|
||||
def analyze_tree(profiles_dir):
|
||||
"""Load every vendor bundle and derive the full filament_id state.
|
||||
|
||||
Returns a dict with the tree-derived snapshot sections plus the working data
|
||||
the checks and the assign pass need. All claims are "Vendor/Filament" strings
|
||||
over INSTANTIATED system filaments, tree-wide including OFL and BBL.
|
||||
Returns a dict of the tree-derived state the checks and the assign pass need,
|
||||
tree-wide including OFL and BBL.
|
||||
"""
|
||||
profiles_dir = str(profiles_dir)
|
||||
vendor_names = list_vendor_names(profiles_dir)
|
||||
@@ -507,11 +494,6 @@ def analyze_tree(profiles_dir):
|
||||
rec["id_source"] = src
|
||||
vendors[vendor] = filaments
|
||||
|
||||
# id -> set of "Vendor/Filament" claims over instantiated presets. Every id
|
||||
# occurring in the tree is a key; ids only ever DECLARED (e.g. on a root
|
||||
# none of whose descendants instantiate) keep an empty claim list, so that
|
||||
# the snapshot exactly equals the tree-derived state.
|
||||
ids = {}
|
||||
vendor_ids = {} # vendor -> set of ids occurring there (declared or effective)
|
||||
declared_ids = {} # vendor -> set of ids DECLARED in that vendor's own files
|
||||
missing_effective = [] # (vendor, name, file) instantiated presets resolving no id
|
||||
@@ -532,7 +514,6 @@ def analyze_tree(profiles_dir):
|
||||
fid = rec["filament_id"]
|
||||
occurring.add(fid)
|
||||
declared_ids.setdefault(vendor, set()).add(fid)
|
||||
ids.setdefault(fid, set())
|
||||
declarer_triples.append((vendor, rec, fid, triple))
|
||||
triples.setdefault(fid, set()).add(triple)
|
||||
filament_triples.setdefault(
|
||||
@@ -545,11 +526,10 @@ def analyze_tree(profiles_dir):
|
||||
missing_effective.append((vendor, rec["name"], rec["file"]))
|
||||
continue
|
||||
occurring.add(eff)
|
||||
ids.setdefault(eff, set()).add(f"{vendor}/{base_name(rec['name'])}")
|
||||
if not rec.get("filament_id") and OF_ID_RE.match(eff):
|
||||
inherited.append((vendor, rec, eff, triple))
|
||||
|
||||
# Cross-bundle triple divergence (check 4, warning only): the same filament
|
||||
# Cross-bundle triple divergence (check 3, warning only): the same filament
|
||||
# name declared in several bundles with different triples cannot converge
|
||||
# on one id until the divergence is fixed.
|
||||
name_bundles = {}
|
||||
@@ -564,7 +544,6 @@ def analyze_tree(profiles_dir):
|
||||
return {
|
||||
"vendors": vendors,
|
||||
"read_errors": read_errors,
|
||||
"ids": {fid: sorted(claims) for fid, claims in ids.items()},
|
||||
"vendor_ids": vendor_ids,
|
||||
"declared_ids": declared_ids,
|
||||
"missing_effective": sorted(missing_effective),
|
||||
@@ -578,57 +557,16 @@ def analyze_tree(profiles_dir):
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Snapshot IO
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def snapshot_from_analysis(analysis):
|
||||
"""One entry per id, in id order: the product triple it is minted from and
|
||||
the "Vendor/Filament" claims on it. Requires exactly one declared triple per
|
||||
id (update_snapshot refuses any other state; check 3 rejects it anyway)."""
|
||||
ids = {}
|
||||
for fid, claims in sorted(analysis["ids"].items()):
|
||||
[(vendor, ftype, filament_name)] = analysis["triples"][fid]
|
||||
ids[fid] = {"filaments": sorted(claims), "name": filament_name,
|
||||
"filament_type": ftype, "filament_vendor": vendor}
|
||||
return {"ids": ids}
|
||||
|
||||
|
||||
def snapshot_triple(entry):
|
||||
return [entry["filament_vendor"], entry["filament_type"], entry["name"]]
|
||||
|
||||
|
||||
def load_snapshot(path):
|
||||
"""Return the snapshot dict, or None when the file does not exist."""
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
data = load_json(path)
|
||||
data.setdefault("ids", {})
|
||||
return data
|
||||
|
||||
|
||||
def write_snapshot(path, obj):
|
||||
"""Deterministic serialization: snapshot_from_analysis order, indent 1, LF,
|
||||
trailing newline."""
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||||
json.dump(obj, f, indent=1, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# filament_id validation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
map_path=BAMBU_MAP_PATH):
|
||||
def check_filament_ids(profiles_dir=PROFILES_DIR, map_path=BAMBU_MAP_PATH):
|
||||
"""Validate filament_id state across every vendor. Returns the error count.
|
||||
|
||||
1. Format: every id occurring in the tree (declared or effective) must
|
||||
match ^OF[0-9A-Za-z]{6}$. No exceptions: not the snapshot, not BBL.
|
||||
2. Snapshot equality, both directions: every id in the tree, the filaments
|
||||
claiming it and the triple its declarers resolve must equal the snapshot
|
||||
entry exactly (the snapshot diff is the maintainer gate).
|
||||
3. Identity: the id is a function of the triple alone, and there is no
|
||||
match ^OF[0-9A-Za-z]{6}$. No exceptions, not even BBL.
|
||||
2. Identity: the id is a function of the triple alone, and there is no
|
||||
second acceptable value. (a) A declared id must equal the one id the
|
||||
declarer's own triple mints; (b) the id an instantiated preset inherits
|
||||
must equal the one ITS own triple mints — how it inherits it (a root, a
|
||||
@@ -636,31 +574,22 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
filament resolves an effective id at all (an id-less one is a hard load
|
||||
error in C++); (d) no two products mint one id (a base62 collision,
|
||||
resolved by renaming one of them).
|
||||
4. Triple integrity: (a) every declarer resolves non-empty filament_vendor
|
||||
3. Triple integrity: (a) every declarer resolves non-empty filament_vendor
|
||||
and filament_type; (b) declarers of one (bundle, filament) resolve
|
||||
identical triples; cross-bundle divergence on the same filament name is a
|
||||
warning only.
|
||||
5. Bambu catalog map: resources/printers/bambu_filament_ids.json must parse,
|
||||
4. Bambu catalog map: resources/printers/bambu_filament_ids.json must parse,
|
||||
carry source/bambustudio_commit/generated, key only OF-format ids, map
|
||||
each Bambu id at most once, and for every row whose key the tree claims,
|
||||
the tree's triple for that id must equal the row's (vendor, type, name).
|
||||
|
||||
Nothing is grandfathered: the snapshot sanctions state, never exceptions.
|
||||
"""
|
||||
_utf8_console()
|
||||
errors = 0
|
||||
analysis = analyze_tree(profiles_dir)
|
||||
snapshot = load_snapshot(snapshot_path)
|
||||
if snapshot is None:
|
||||
print_error(f"filament_id snapshot not found at {snapshot_path}; {UPDATE_HINT}")
|
||||
return 1
|
||||
for msg in analysis["read_errors"]:
|
||||
print_error(msg)
|
||||
errors += 1
|
||||
|
||||
snap_ids = snapshot["ids"]
|
||||
tree_ids = analysis["ids"]
|
||||
|
||||
# -- 1. format ----------------------------------------------------------
|
||||
for vendor in sorted(analysis["vendor_ids"]):
|
||||
for fid in sorted(analysis["vendor_ids"][vendor]):
|
||||
@@ -671,47 +600,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
f'filament ids must come from "{GENERATE_CMD}"')
|
||||
errors += 1
|
||||
|
||||
# -- 2. snapshot equality (both directions) -----------------------------
|
||||
tree_triples = analysis["triples"]
|
||||
for fid in sorted(tree_ids):
|
||||
entry = snap_ids.get(fid)
|
||||
if entry is None:
|
||||
print_error(
|
||||
f'filament_id "{fid}" is not sanctioned by '
|
||||
f"scripts/filament_id_snapshot.json; {UPDATE_HINT}")
|
||||
errors += 1
|
||||
continue
|
||||
for claim in tree_ids[fid]:
|
||||
if claim not in entry["filaments"]:
|
||||
print_error(
|
||||
f'filament_id "{fid}" claim "{claim}" is not sanctioned by '
|
||||
f"scripts/filament_id_snapshot.json; {UPDATE_HINT}")
|
||||
errors += 1
|
||||
# Every tree id has at least one declarer; the snapshot records one
|
||||
# triple per id, so a divergent declarer is a mismatch in both directions.
|
||||
sanctioned = snapshot_triple(entry)
|
||||
for t in tree_triples[fid]:
|
||||
if t != sanctioned:
|
||||
print_error(
|
||||
f'filament_id "{fid}" triple "{"/".join(t)}" is not sanctioned by '
|
||||
f'scripts/filament_id_snapshot.json, which records '
|
||||
f'"{"/".join(sanctioned)}"; {UPDATE_HINT}')
|
||||
errors += 1
|
||||
for fid in sorted(snap_ids):
|
||||
if fid not in tree_ids:
|
||||
print_error(
|
||||
f'filament_id stability: snapshot id "{fid}" vanished from the tree; '
|
||||
f"{UPDATE_HINT}")
|
||||
errors += 1
|
||||
continue
|
||||
for claim in snap_ids[fid]["filaments"]:
|
||||
if claim not in tree_ids[fid]:
|
||||
print_error(
|
||||
f'filament_id stability: snapshot claim "{claim}" of id "{fid}" '
|
||||
f"vanished from the tree; {UPDATE_HINT}")
|
||||
errors += 1
|
||||
|
||||
# -- 3. identity: the id is a function of the triple alone ---------------
|
||||
# -- 2. identity: the id is a function of the triple alone ---------------
|
||||
# One triple, one id: a declaration must carry exactly the mint of its
|
||||
# triple, and there is no second acceptable value — not a salt, not a
|
||||
# hand-picked one, not whatever another preset of the product carries. Two
|
||||
@@ -727,10 +616,9 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
f'filament_id "{fid}" declared by "{rec["name"]}" ({rec["file"]}) does '
|
||||
f'not match the mint of its triple "{"/".join(triple)}": expected '
|
||||
f'"{want}"; paste the expected id, or fix the triple and run '
|
||||
f'"{GENERATE_CMD} --vendor {vendor}" (preview with --dry-run), then '
|
||||
f"--update-snapshot")
|
||||
f'"{GENERATE_CMD} --vendor {vendor}" (preview with --dry-run)')
|
||||
errors += 1
|
||||
# (3b) An inherited id is held to the same single value, and every preset
|
||||
# (2b) An inherited id is held to the same single value, and every preset
|
||||
# missing it is listed — a variant under a wrong root as much as a preset
|
||||
# riding another product's root. Nothing is folded into the declarer's
|
||||
# error: the report names each preset whose id is wrong.
|
||||
@@ -755,7 +643,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
f'run "{GENERATE_CMD}" (expected id for filament '
|
||||
f'"{vendor}/{base_name(name)}": "{expected}")')
|
||||
errors += 1
|
||||
# (3d) The mint is injective over the tree's products, or two of them are
|
||||
# (2d) The mint is injective over the tree's products, or two of them are
|
||||
# indistinguishable to every device that matches on the id.
|
||||
for fid, ts in sorted(analysis["collisions"].items()):
|
||||
print_error(
|
||||
@@ -764,7 +652,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
f"of them so their triples differ")
|
||||
errors += 1
|
||||
|
||||
# -- 4. triple integrity ---------------------------------------------------
|
||||
# -- 3. triple integrity ---------------------------------------------------
|
||||
for vendor, rec, fid, triple in sorted(
|
||||
analysis["declarer_triples"], key=lambda x: (x[0], x[1]["file"])):
|
||||
if triple[0] and triple[1]:
|
||||
@@ -797,7 +685,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
f"({detail}); bundles of one product converge on one id only once "
|
||||
f"their triples agree")
|
||||
|
||||
# -- 6. Bambu catalog map --------------------------------------------------
|
||||
# -- 4. Bambu catalog map --------------------------------------------------
|
||||
try:
|
||||
bambu_map = load_json(map_path)
|
||||
if not isinstance(bambu_map, dict):
|
||||
@@ -838,7 +726,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
||||
errors += 1
|
||||
else:
|
||||
bambu_id_owners[bambu_id] = fid
|
||||
claimed = tree_triples.get(fid)
|
||||
claimed = analysis["triples"].get(fid)
|
||||
if not claimed:
|
||||
continue # a product BambuStudio ships that the tree does not (yet)
|
||||
row_triple = [row.get("vendor", ""), row.get("type", ""), row.get("name", "")]
|
||||
@@ -1404,7 +1292,7 @@ def check_normalized(profiles_dir, vendor):
|
||||
# check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def check_profiles(profiles_dir=PROFILES_DIR, vendors=None, snapshot_path=SNAPSHOT_PATH):
|
||||
def check_profiles(profiles_dir=PROFILES_DIR, vendors=None):
|
||||
"""Validate the whole profile tree. Returns the error count.
|
||||
|
||||
The per-vendor checks honour `vendors`; the setting_id and filament_id checks are
|
||||
@@ -1468,7 +1356,7 @@ def check_profiles(profiles_dir=PROFILES_DIR, vendors=None, snapshot_path=SNAPSH
|
||||
# Cross-vendor checks: setting_id uniqueness and the whole filament_id state,
|
||||
# both validated over the entire tree regardless of --vendor.
|
||||
errors_found += check_setting_id_uniqueness(profiles_dir)
|
||||
errors_found += check_filament_ids(profiles_dir, snapshot_path)
|
||||
errors_found += check_filament_ids(profiles_dir)
|
||||
|
||||
print("\n==================== SUMMARY ====================")
|
||||
print_info(f"Checked vendors : {len(checked)}")
|
||||
@@ -1486,69 +1374,6 @@ def check_profiles(profiles_dir=PROFILES_DIR, vendors=None, snapshot_path=SNAPSH
|
||||
return errors_found
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# update-snapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def update_snapshot(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH, dry_run=False):
|
||||
"""Regenerate the snapshot from the tree.
|
||||
|
||||
Refuses to sanction a tree it could not read whole, and an id declared under
|
||||
more than one triple: neither state can be recorded truthfully, so writing it
|
||||
would only hide the mistake until CI. It does not judge the ids themselves —
|
||||
the snapshot records state and check judges it, so an id that is not a mint
|
||||
lands in the diff and fails check 1.
|
||||
Idempotent: a second run over an unchanged tree changes nothing. Returns 0
|
||||
on success.
|
||||
"""
|
||||
analysis = analyze_tree(profiles_dir)
|
||||
# A tree that could not be read whole cannot be sanctioned: the snapshot
|
||||
# would silently drop the unreadable bundle's ids and claims, and the diff
|
||||
# would read as a deliberate removal.
|
||||
refusals = len(analysis["read_errors"])
|
||||
for msg in analysis["read_errors"]:
|
||||
print_error(msg)
|
||||
|
||||
for fid, ts in sorted(analysis["triples"].items()):
|
||||
if len(ts) > 1:
|
||||
print_error(
|
||||
f'refusing to sanction filament_id "{fid}": declared under {len(ts)} '
|
||||
f'triples ({"; ".join("/".join(t) for t in ts)}); one id names one '
|
||||
f"product (check 3)")
|
||||
refusals += 1
|
||||
if refusals:
|
||||
return 1
|
||||
|
||||
new_snap = snapshot_from_analysis(analysis)
|
||||
old_snap = load_snapshot(snapshot_path)
|
||||
old_ids = old_snap["ids"] if old_snap else {}
|
||||
|
||||
# Diff summary.
|
||||
added_ids = sorted(set(new_snap["ids"]) - set(old_ids))
|
||||
removed_ids = sorted(set(old_ids) - set(new_snap["ids"]))
|
||||
added_claims = sum(
|
||||
len(set(entry["filaments"]) - set(old_ids.get(fid, {}).get("filaments", [])))
|
||||
for fid, entry in new_snap["ids"].items())
|
||||
removed_claims = sum(
|
||||
len(set(entry["filaments"]) - set(new_snap["ids"].get(fid, {}).get("filaments", [])))
|
||||
for fid, entry in old_ids.items())
|
||||
changed = new_snap != (old_snap or {"ids": {}})
|
||||
|
||||
if changed and not dry_run:
|
||||
write_snapshot(snapshot_path, new_snap)
|
||||
|
||||
print_info(f"snapshot ids : {len(new_snap['ids'])} (+{len(added_ids)} / -{len(removed_ids)})")
|
||||
print_info(f"claims added : {added_claims}")
|
||||
print_info(f"claims removed : {removed_claims}")
|
||||
if changed and dry_run:
|
||||
print_success(f"dry run: {snapshot_path} would be rewritten; nothing written")
|
||||
elif changed:
|
||||
print_success(f"snapshot written to {snapshot_path}")
|
||||
else:
|
||||
print_success("snapshot already up to date; nothing changed")
|
||||
return 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Byte-preserving profile edits
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1692,9 +1517,9 @@ def generate_filament_ids(profiles_dir=PROFILES_DIR, vendors=None, dry_run=False
|
||||
* an instantiated filament that resolves no id at all gets one inserted
|
||||
into its root(s): the id-less presets of the SAME filament its members
|
||||
inherit, or the member itself (a parent of another filament cannot carry
|
||||
this filament's id — check 3).
|
||||
this filament's id — check 2).
|
||||
A declaration is left alone exactly when it already equals the one id its
|
||||
triple mints (check 3). Two products minting one id (check 3d) are reported
|
||||
triple mints (check 2). Two products minting one id (check 2d) are reported
|
||||
and left unwritten: nothing salts past a collision, a rename resolves it.
|
||||
|
||||
`vendors` restricts what is WRITTEN; the id is a function of the triple
|
||||
@@ -1702,9 +1527,7 @@ def generate_filament_ids(profiles_dir=PROFILES_DIR, vendors=None, dry_run=False
|
||||
reports whatever it was not allowed to touch. `changed_paths`, when a set is
|
||||
passed, collects the files that changed. A file whose layout offers no
|
||||
anchor for the edit is reported and counted as an error, so one odd profile
|
||||
cannot abort the pass over all the others. Never reads or touches the
|
||||
snapshot — run --update-snapshot afterwards and review the diff. Returns
|
||||
(files_changed, errors).
|
||||
cannot abort the pass over all the others. Returns (files_changed, errors).
|
||||
"""
|
||||
_utf8_console()
|
||||
analysis = analyze_tree(profiles_dir)
|
||||
@@ -1959,9 +1782,9 @@ def run_generate_id(profiles_dir, vendors, filament_id, setting_id, dry_run):
|
||||
do_filament = filament_id or not setting_id
|
||||
do_setting = setting_id or not filament_id
|
||||
changed = set() # one file the two passes both touch is still one file
|
||||
filament_files = errors = 0
|
||||
errors = 0
|
||||
if do_filament:
|
||||
filament_files, e = generate_filament_ids(profiles_dir, vendors, dry_run, changed)
|
||||
_n, e = generate_filament_ids(profiles_dir, vendors, dry_run, changed)
|
||||
errors += e
|
||||
if do_setting:
|
||||
_n, e = generate_setting_ids(profiles_dir, vendors, dry_run, changed)
|
||||
@@ -1973,11 +1796,6 @@ def run_generate_id(profiles_dir, vendors, filament_id, setting_id, dry_run):
|
||||
print_error(f"{summary}; {errors} error(s)")
|
||||
else:
|
||||
print_success(summary)
|
||||
if filament_files and not dry_run:
|
||||
# A filament_id write may or may not move the sanctioned state (an id repaired
|
||||
# back to the value the snapshot already records does not), so regenerate and
|
||||
# let the diff - empty or not - say.
|
||||
print_warning(f"now {UPDATE_HINT}")
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
@@ -2439,13 +2257,11 @@ examples:
|
||||
preview exactly that; writes nothing
|
||||
orca_profile_tool.py generate-id --setting-id --vendor Elegoo
|
||||
setting_id only, and only in that bundle
|
||||
orca_profile_tool.py update-snapshot
|
||||
re-record the sanctioned filament_id state after a generate-id run
|
||||
|
||||
after adding, renaming or deleting profile files, run in this order:
|
||||
normalize -> update-index -> generate-id -> update-snapshot -> check
|
||||
normalize -> update-index -> generate-id -> check
|
||||
normalize supplies missing types; update-index registers presets before id
|
||||
generation. update-snapshot is needed when filament ids or claims change.
|
||||
generation.
|
||||
Use trim only for deliberate cleanup, previewed with --dry-run: it judges against
|
||||
the current index and can delete newly added, unindexed presets.
|
||||
"""
|
||||
@@ -2473,12 +2289,6 @@ def build_parser():
|
||||
dry_run_opt.add_argument("--dry-run", "--dryrun", dest="dry_run", action="store_true",
|
||||
help="report what would change and write nothing")
|
||||
|
||||
snapshot_opt = argparse.ArgumentParser(add_help=False)
|
||||
snapshot_opt.add_argument("--snapshot", default=None, metavar="PATH",
|
||||
help="the sanctioned filament_id state of that tree "
|
||||
"(default: scripts/filament_id_snapshot.json, which "
|
||||
"describes resources/profiles and no other tree)")
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="orca_profile_tool.py", allow_abbrev=False,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
@@ -2496,7 +2306,7 @@ def build_parser():
|
||||
allow_abbrev=False, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
|
||||
add(
|
||||
"check", [vendor_opt, snapshot_opt, profiles_opt],
|
||||
"check", [vendor_opt, profiles_opt],
|
||||
"validate the whole profile tree -- what CI runs",
|
||||
"Validate the whole profile tree: preset name uniqueness, index coverage\n"
|
||||
"both ways, compatible_printers, default-material references, obsolete,\n"
|
||||
@@ -2560,11 +2370,6 @@ def build_parser():
|
||||
"Use trim only for deliberate unindexed-file cleanup, previewed with\n"
|
||||
"--dry-run; it can also delete newly authored presets.")
|
||||
|
||||
add("update-snapshot", [dry_run_opt, snapshot_opt, profiles_opt],
|
||||
"re-record scripts/filament_id_snapshot.json",
|
||||
"Re-record the sanctioned filament_id state after a generate-id run, and\n"
|
||||
"commit the diff for maintainer review.")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
@@ -2590,29 +2395,14 @@ def main(argv=None):
|
||||
return 1
|
||||
profile_types = tuple(getattr(args, "profile_type", []) or ()) or None
|
||||
|
||||
snapshot_path = getattr(args, "snapshot", None)
|
||||
if snapshot_path is None:
|
||||
if (args.command in ("check", "update-snapshot")
|
||||
and os.path.abspath(profiles_dir) != os.path.abspath(PROFILES_DIR)):
|
||||
# The repo snapshot is the sanctioned state of resources/profiles alone:
|
||||
# checking another tree against it is meaningless, and re-recording one
|
||||
# into it would overwrite the tracked file with a foreign tree's state.
|
||||
parser.error(f"{args.command} reads and writes the sanctioned state of the "
|
||||
f"tree it is given, so --profiles needs --snapshot PATH for "
|
||||
f"that tree too")
|
||||
snapshot_path = SNAPSHOT_PATH
|
||||
|
||||
if args.command == "check":
|
||||
errors = check_profiles(profiles_dir, vendors, snapshot_path)
|
||||
errors = check_profiles(profiles_dir, vendors)
|
||||
return 1 if errors else 0
|
||||
|
||||
if args.command == "generate-id":
|
||||
return run_generate_id(profiles_dir, vendors, args.filament_id, args.setting_id,
|
||||
args.dry_run)
|
||||
|
||||
if args.command == "update-snapshot":
|
||||
return update_snapshot(profiles_dir, snapshot_path, dry_run=args.dry_run)
|
||||
|
||||
if args.command == "normalize":
|
||||
_changed, errors = normalize_profiles(profiles_dir, vendors, profile_types,
|
||||
force=args.force, dry_run=args.dry_run)
|
||||
|
||||
@@ -55,13 +55,12 @@ def preset(name, filament_id=None, inherits=None, instantiation=True,
|
||||
|
||||
|
||||
class SyntheticTree:
|
||||
"""A throwaway resources/profiles-shaped directory plus a snapshot path."""
|
||||
"""A throwaway resources/profiles-shaped directory."""
|
||||
|
||||
def __init__(self):
|
||||
self.dir = tempfile.mkdtemp(prefix="filament_id_test_")
|
||||
self.profiles = os.path.join(self.dir, "profiles")
|
||||
os.makedirs(self.profiles)
|
||||
self.snapshot = os.path.join(self.dir, "filament_id_snapshot.json")
|
||||
|
||||
def cleanup(self):
|
||||
shutil.rmtree(self.dir, ignore_errors=True)
|
||||
@@ -110,16 +109,6 @@ class SyntheticTree:
|
||||
with open(idx_path, "w", encoding="utf-8") as f:
|
||||
json.dump(index, f, indent=4, ensure_ascii=False)
|
||||
|
||||
def remove_preset(self, vendor, name):
|
||||
os.remove(self.preset_path(vendor, name))
|
||||
idx_path = os.path.join(self.profiles, vendor + ".json")
|
||||
with open(idx_path, encoding="utf-8") as f:
|
||||
index = json.load(f)
|
||||
index["filament_list"] = [
|
||||
e for e in index["filament_list"] if e["name"] != name]
|
||||
with open(idx_path, "w", encoding="utf-8") as f:
|
||||
json.dump(index, f, indent=4, ensure_ascii=False)
|
||||
|
||||
def bytes_map(self):
|
||||
"""{relative path -> file bytes} over every .json in the tree."""
|
||||
raw = {}
|
||||
@@ -135,17 +124,11 @@ class SyntheticTree:
|
||||
|
||||
# -- pipeline wrappers ---------------------------------------------------
|
||||
|
||||
def update_snapshot(self, dry_run=False):
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
rc = afi.update_snapshot(self.profiles, self.snapshot, dry_run)
|
||||
return rc, buf.getvalue()
|
||||
|
||||
def check(self, map_path=None):
|
||||
buf = io.StringIO()
|
||||
kwargs = {} if map_path is None else {"map_path": map_path}
|
||||
with contextlib.redirect_stdout(buf):
|
||||
errors = afi.check_filament_ids(self.profiles, self.snapshot, **kwargs)
|
||||
errors = afi.check_filament_ids(self.profiles, **kwargs)
|
||||
return errors, buf.getvalue()
|
||||
|
||||
# assign() and remint() are the same one pass over the tree — every filament
|
||||
@@ -167,8 +150,6 @@ class SyntheticTree:
|
||||
def cli(self, *argv):
|
||||
"""Run main() against this tree, capturing stdout."""
|
||||
flags = [*argv, "--profiles", self.profiles]
|
||||
if argv and argv[0] in ("check", "update-snapshot"):
|
||||
flags += ["--snapshot", self.snapshot]
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
rc = afi.main(flags)
|
||||
@@ -178,10 +159,10 @@ class SyntheticTree:
|
||||
def make_clean_tree(apla_id="AX01", generic_id="OGFL99"):
|
||||
"""Baseline tree: OFL base+generic, a vendor filament, a clean tuned generic.
|
||||
|
||||
apla_id/generic_id default to arbitrary non-OF placeholders (sanctioned by
|
||||
the snapshot below) since most tests only need "already assigned, don't
|
||||
touch" and never run the checks. TestAssign and the check tests pass real
|
||||
OF-format ids instead (OfCleanTreeCase).
|
||||
apla_id/generic_id default to arbitrary non-OF placeholders since most tests
|
||||
only need "already assigned, don't touch" and never run the checks.
|
||||
TestAssign and the check tests pass real OF-format ids instead
|
||||
(OfCleanTreeCase).
|
||||
"""
|
||||
t = SyntheticTree()
|
||||
t.add_vendor(OFL, [
|
||||
@@ -199,8 +180,6 @@ def make_clean_tree(apla_id="AX01", generic_id="OGFL99"):
|
||||
preset("Generic PLA @P1", inherits="Generic PLA @System",
|
||||
compatible_printers=["P1 0.4 nozzle"]),
|
||||
])
|
||||
rc, _out = t.update_snapshot()
|
||||
assert rc == 0
|
||||
return t
|
||||
|
||||
|
||||
@@ -212,10 +191,9 @@ class SyntheticTreeCase(unittest.TestCase):
|
||||
|
||||
class OfCleanTreeCase(unittest.TestCase):
|
||||
"""Like SyntheticTreeCase, but the baseline filament/generic already carry
|
||||
real OF-format ids (check 1 now rejects "AX01"/"OGFL99" unconditionally,
|
||||
with no snapshot exemption), so an otherwise-untouched tree still passes
|
||||
check_filament_ids. Tests that specifically need a non-OF baseline to
|
||||
remint (TestRemint, TestUpdateSnapshot) keep using SyntheticTreeCase
|
||||
real OF-format ids (check 1 rejects "AX01"/"OGFL99"), so an
|
||||
otherwise-untouched tree passes check_filament_ids. Tests that specifically
|
||||
need a non-OF baseline to remint (TestRemint) keep using SyntheticTreeCase
|
||||
instead.
|
||||
"""
|
||||
def setUp(self):
|
||||
@@ -231,7 +209,7 @@ class OfCleanTreeCase(unittest.TestCase):
|
||||
|
||||
class TestMint(unittest.TestCase):
|
||||
def test_namespace_literal(self):
|
||||
# Frozen: derived from the setting_id namespace; baked into the snapshot.
|
||||
# Frozen: derived from the setting_id namespace; baked into every shipped id.
|
||||
self.assertEqual(afi.FILAMENT_ID_NAMESPACE,
|
||||
uuid.UUID("c4d3ff49-4c32-5534-a3e3-00894157ab97"))
|
||||
|
||||
@@ -450,22 +428,10 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn('is not a minted "OF" id', out)
|
||||
self.assertIn("BOGUS_9", out)
|
||||
|
||||
def test_check2_new_claim_needs_snapshot_update(self):
|
||||
self.t.write_preset("VendorA", preset("ANEW @P2", inherits="APLA @base",
|
||||
compatible_printers=["P2"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn('claim "VendorA/ANEW" is not sanctioned', out)
|
||||
self.assertIn("update-snapshot", out)
|
||||
|
||||
def test_check2_vanished_claim_is_stability_error(self):
|
||||
self.t.remove_preset("VendorA", "APLA @P1")
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn("stability", out)
|
||||
self.assertIn('"VendorA/APLA"', out)
|
||||
|
||||
def test_check2_triple_change_needs_snapshot_update(self):
|
||||
def test_check2_triple_change_needs_a_remint(self):
|
||||
# Correcting a triple changes the product's identity: the old id is no
|
||||
# longer its mint, reported on the root and again under the variant
|
||||
# inheriting it, until generate-id re-mints it.
|
||||
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
|
||||
self.t.write_preset("VendorA", preset("APLA @base", filament_id=apla_id,
|
||||
instantiation=False,
|
||||
@@ -473,26 +439,15 @@ class TestChecks(OfCleanTreeCase):
|
||||
filament_type="PETG"),
|
||||
register=False)
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn('triple "AVendor/PETG/APLA" is not sanctioned', out)
|
||||
self.assertIn('which records "AVendor/PLA/APLA"', out)
|
||||
# Sanctioning the new triple is not enough: the old id is no longer its
|
||||
# mint (check 3, nothing grandfathered) — the identity fix is a re-mint,
|
||||
# reported on the root and again under the variant inheriting it.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 2, out)
|
||||
self.assertIn("does not match the mint of its triple", out)
|
||||
self.assertIn('"APLA @P1" (VendorA/filament/APLA @P1.json) inherits filament_id', out)
|
||||
_changed, errors, out = self.t.remint(["VendorA"])
|
||||
self.assertEqual(errors, 0, out)
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
def test_check3_of_id_must_match_triple_mint(self):
|
||||
def test_check2_of_id_must_match_triple_mint(self):
|
||||
self.t.write_preset("VendorA", preset("BNEW @base", filament_id="OFZZZZZZ",
|
||||
instantiation=False,
|
||||
filament_vendor="BV", filament_type="PLA"))
|
||||
@@ -503,39 +458,20 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn("does not match the mint of its triple", out)
|
||||
self.assertIn(afi.generate_filament_id("BV", "PLA", "BNEW"), out)
|
||||
|
||||
def test_check3_no_grandfathering_of_a_wrong_declaration(self):
|
||||
# Sanctioning the tree does not excuse a declaration from its mint.
|
||||
self.t.write_preset("VendorA", preset("CNEW @base", filament_id="OFZZZZZZ",
|
||||
instantiation=False,
|
||||
filament_vendor="CV", filament_type="PLA"))
|
||||
self.t.write_preset("VendorA", preset("CNEW @P1", inherits="CNEW @base",
|
||||
compatible_printers=["P1"]))
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 2, out) # the declaration, and the variant inheriting it
|
||||
self.assertIn("does not match the mint of its triple", out)
|
||||
self.assertIn('"CNEW @P1" (VendorA/filament/CNEW @P1.json) inherits filament_id', out)
|
||||
|
||||
def test_check3_inherited_id_must_be_the_mint_of_own_triple(self):
|
||||
def test_check2_inherited_id_must_be_the_mint_of_own_triple(self):
|
||||
# A preset of another filament inheriting APLA's root takes APLA's id,
|
||||
# which is not the mint of ITS triple (AVendor/PLA/Tuned PLA).
|
||||
self.t.write_preset("VendorA", preset("Tuned PLA @P1", inherits="APLA @base",
|
||||
compatible_printers=["P1"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn('"Tuned PLA @P1" (VendorA/filament/Tuned PLA @P1.json) inherits '
|
||||
'filament_id "%s"' % afi.generate_filament_id("AVendor", "PLA", "APLA"),
|
||||
out)
|
||||
self.assertIn('mints "%s"' % afi.generate_filament_id("AVendor", "PLA", "Tuned PLA"),
|
||||
out)
|
||||
# ... and sanctioning the tree does not excuse it either.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1, out)
|
||||
|
||||
def test_check3_lists_every_preset_inheriting_a_wrong_id(self):
|
||||
def test_check2_lists_every_preset_inheriting_a_wrong_id(self):
|
||||
# A wrong declaration is reported under every preset inheriting it, its
|
||||
# own product's variant and another product alike: each one's effective
|
||||
# id is not the mint of its own triple, and each is listed. Nothing is
|
||||
@@ -552,11 +488,10 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn('"DNEW @P1" (VendorA/filament/DNEW @P1.json) inherits filament_id', out)
|
||||
self.assertIn('"Other DNEW @P1" (VendorA/filament/Other DNEW @P1.json) inherits '
|
||||
'filament_id', out)
|
||||
# The unsanctioned id (check 2), the declaration (3a), and both presets
|
||||
# inheriting it (3b).
|
||||
self.assertEqual(errors, 4, out)
|
||||
# The declaration (2a), and both presets inheriting it (2b).
|
||||
self.assertEqual(errors, 3, out)
|
||||
|
||||
def test_check3_reports_an_inherited_mismatch_even_when_its_own_product_misdeclares_the_id(self):
|
||||
def test_check2_reports_an_inherited_mismatch_even_when_its_own_product_misdeclares_the_id(self):
|
||||
# "Tuned PLA @P1" inherits APLA's root, so it carries APLA's id: wrong
|
||||
# for its own product however the declarations around it are fixed.
|
||||
# That "Tuned PLA @base" — its own product — misdeclares that same id
|
||||
@@ -573,11 +508,11 @@ class TestChecks(OfCleanTreeCase):
|
||||
'not match the mint of its triple', out)
|
||||
self.assertIn('"Tuned PLA @P1" (VendorA/filament/Tuned PLA @P1.json) inherits '
|
||||
'filament_id', out)
|
||||
# The unsanctioned claim and triple (check 2), the declaration (3a) and
|
||||
# the inherited id (3b): four distinct errors, nothing folded away.
|
||||
self.assertEqual(errors, 4, out)
|
||||
# The declaration (2a) and the inherited id (2b): two distinct errors,
|
||||
# nothing folded away.
|
||||
self.assertEqual(errors, 2, out)
|
||||
|
||||
def test_check3_reports_a_collision_between_two_products(self):
|
||||
def test_check2_reports_a_collision_between_two_products(self):
|
||||
# Two products whose triples mint one id is a base62 collision. There
|
||||
# is no salted or hand-picked second id to fall back on: the check
|
||||
# names both products, and the remedy is a rename so the triples differ.
|
||||
@@ -601,13 +536,12 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn("V/PLA/X", out)
|
||||
self.assertIn("W/ABS/Y", out)
|
||||
# Each declaration is the mint of its own triple, so the collision is
|
||||
# the only identity error — no product is pushed off its id — and the
|
||||
# unsanctioned id (check 2) is the only other one.
|
||||
# the only error — no product is pushed off its id.
|
||||
self.assertNotIn("does not match the mint", out)
|
||||
self.assertNotIn("inherits filament_id", out)
|
||||
self.assertEqual(errors, 2, out)
|
||||
self.assertEqual(errors, 1, out)
|
||||
|
||||
def test_check3_renamed_tuned_generic_is_an_identity_error(self):
|
||||
def test_check2_renamed_tuned_generic_is_an_identity_error(self):
|
||||
# Riding the OFL generic under another base name: same rule, same error.
|
||||
self.t.write_preset("VendorA", preset("Tuned PLA @P1",
|
||||
inherits="Generic PLA @System",
|
||||
@@ -617,7 +551,7 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn("Tuned PLA @P1", out)
|
||||
self.assertIn("inherits filament_id", out)
|
||||
|
||||
def test_check3_own_key_on_an_instantiated_preset_is_fine(self):
|
||||
def test_check2_own_key_on_an_instantiated_preset_is_fine(self):
|
||||
# Where the id comes from is irrelevant: a variant may carry the key.
|
||||
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
|
||||
self.t.write_preset("VendorA", preset("APLA @P1", filament_id=apla_id,
|
||||
@@ -627,7 +561,7 @@ class TestChecks(OfCleanTreeCase):
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
def test_check3_inheriting_a_real_filament_of_another_product_is_fine(self):
|
||||
def test_check2_inheriting_a_real_filament_of_another_product_is_fine(self):
|
||||
# A branded product may inherit the OFL generic (an instantiated
|
||||
# preset) for its settings; it declares its own triple's id.
|
||||
fid = afi.generate_filament_id("BV", "PLA", "Branded PLA")
|
||||
@@ -635,8 +569,6 @@ class TestChecks(OfCleanTreeCase):
|
||||
inherits="Generic PLA @System",
|
||||
filament_vendor="BV",
|
||||
compatible_printers=["P1"]))
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
# With a wrong key it is a plain mint mismatch: the parent plays no
|
||||
@@ -670,22 +602,16 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn(f'filament_id "{fid}"', out)
|
||||
self.assertIn('is not a minted "OF" id', out)
|
||||
|
||||
def test_check3c_unresolvable_instantiated_filament(self):
|
||||
def test_check2c_unresolvable_instantiated_filament(self):
|
||||
self.t.write_preset("VendorA", preset("DNEW @P1", compatible_printers=["P1"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn("resolves no filament_id", out)
|
||||
self.assertIn("hard load error", out)
|
||||
|
||||
def test_missing_snapshot_is_an_error(self):
|
||||
os.remove(self.t.snapshot)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1)
|
||||
self.assertIn("snapshot not found", out)
|
||||
|
||||
|
||||
class TestCheck5(OfCleanTreeCase):
|
||||
def test_5a_empty_vendor_is_hard_error(self):
|
||||
class TestCheck3(OfCleanTreeCase):
|
||||
def test_3a_empty_vendor_is_hard_error(self):
|
||||
fid = afi.generate_filament_id("", "PLA", "NVPLA")
|
||||
self.t.write_preset("VendorA", preset("NVPLA @base", filament_id=fid,
|
||||
instantiation=False,
|
||||
@@ -693,17 +619,11 @@ class TestCheck5(OfCleanTreeCase):
|
||||
self.t.write_preset("VendorA", preset("NVPLA @P1", inherits="NVPLA @base",
|
||||
compatible_printers=["P1"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn("resolves empty filament_vendor", out)
|
||||
self.assertIn('filament_vendor "Generic"', out)
|
||||
# No grandfathering: sanctioning the tree does not silence check 4a.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn("resolves empty filament_vendor", out)
|
||||
self.assertIn('filament_vendor "Generic"', out)
|
||||
|
||||
def test_5b_divergent_filament_triples(self):
|
||||
def test_3b_divergent_filament_triples(self):
|
||||
id1 = afi.generate_filament_id("MV", "PLA", "MPLA")
|
||||
id2 = afi.generate_filament_id("MV", "PETG", "MPLA")
|
||||
self.t.write_preset("VendorA", preset("MPLA @base1", filament_id=id1,
|
||||
@@ -715,18 +635,12 @@ class TestCheck5(OfCleanTreeCase):
|
||||
filament_vendor="MV",
|
||||
filament_type="PETG"))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn("divergent triples", out)
|
||||
self.assertIn("MV/PLA/MPLA", out)
|
||||
self.assertIn("MV/PETG/MPLA", out)
|
||||
# No grandfathering: sanctioning the tree does not silence check 4b.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn("divergent triples", out)
|
||||
|
||||
def test_5_cross_bundle_divergence_is_warning_only(self):
|
||||
def test_3_cross_bundle_divergence_is_warning_only(self):
|
||||
fid = afi.generate_filament_id("BV", "PETG", "APLA")
|
||||
self.t.add_vendor("VendorB", [
|
||||
preset("APLA @base", filament_id=fid, instantiation=False,
|
||||
@@ -734,8 +648,6 @@ class TestCheck5(OfCleanTreeCase):
|
||||
preset("APLA @PB", inherits="APLA @base",
|
||||
compatible_printers=["PB 0.4 nozzle"]),
|
||||
])
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
self.assertIn("[WARNING]", out)
|
||||
@@ -743,7 +655,7 @@ class TestCheck5(OfCleanTreeCase):
|
||||
self.assertIn('"APLA"', out)
|
||||
|
||||
|
||||
class TestCheck6(OfCleanTreeCase):
|
||||
class TestCheck4(OfCleanTreeCase):
|
||||
def _write_map(self, rows):
|
||||
path = os.path.join(self.t.dir, "bambu_filament_ids.json")
|
||||
ubfi.write_map(path, rows, "testcommit", "2026-09-04")
|
||||
@@ -845,104 +757,6 @@ class TestCheck6(OfCleanTreeCase):
|
||||
self.assertIn('declares no "bambu_id"', out)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --update-snapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestUpdateSnapshot(SyntheticTreeCase):
|
||||
def test_idempotent_and_deterministic(self):
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
first = f.read()
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("nothing changed", out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), first)
|
||||
self.assertTrue(first.endswith(b"\n"))
|
||||
self.assertNotIn(b"\r", first)
|
||||
snap = json.loads(first.decode("utf-8"))
|
||||
self.assertEqual(list(snap), ["ids"]) # state only, no exception lists
|
||||
self.assertEqual(list(snap["ids"]), sorted(snap["ids"]))
|
||||
self.assertEqual(snap["ids"]["AX01"], {
|
||||
"filaments": ["VendorA/APLA"], "name": "APLA",
|
||||
"filament_type": "PLA", "filament_vendor": "AVendor"})
|
||||
self.assertEqual(snap["ids"]["OGFL99"], {
|
||||
"filaments": ["OrcaFilamentLibrary/Generic PLA", "VendorA/Generic PLA"],
|
||||
"name": "Generic PLA", "filament_type": "PLA", "filament_vendor": "Generic"})
|
||||
# Key order is part of the on-disk format.
|
||||
self.assertEqual(list(snap["ids"]["AX01"]),
|
||||
["filaments", "name", "filament_type", "filament_vendor"])
|
||||
|
||||
def test_refuses_a_tree_it_could_not_read(self):
|
||||
# A bundle that does not parse contributes no ids, so sanctioning the
|
||||
# rest would record the loss as a deliberate removal.
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
before = f.read()
|
||||
with open(os.path.join(self.t.profiles, "VendorA",
|
||||
"filament", "APLA @base.json"), "w",
|
||||
encoding="utf-8") as f:
|
||||
f.write("{ not json")
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn("unreadable filament profile", out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), before)
|
||||
|
||||
def test_refuses_an_id_declared_under_two_triples(self):
|
||||
# VendorB re-declares APLA's id for a different product: one id, two
|
||||
# triples. No single entry can describe it, and check 3 rejects it anyway.
|
||||
self.t.add_vendor("VendorB", [
|
||||
preset("BPLA @base", filament_id="AX01", instantiation=False,
|
||||
filament_vendor="BVendor", filament_type="PLA"),
|
||||
preset("BPLA @P1", inherits="BPLA @base", compatible_printers=["P1"]),
|
||||
])
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
before = f.read()
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 1)
|
||||
self.assertIn('refusing to sanction filament_id "AX01": declared under 2 triples '
|
||||
'(AVendor/PLA/APLA; BVendor/PLA/BPLA)', out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), before) # nothing written on refusal
|
||||
|
||||
def test_records_an_id_it_cannot_defend_and_lets_check_reject_it(self):
|
||||
# update-snapshot records state, it does not judge ids: a foreign id
|
||||
# lands in the diff a maintainer reviews, and fails check 1 straight
|
||||
# after. Sanctioning it does not grandfather it.
|
||||
self.t.write_preset("VendorA", preset("CNEW @base", filament_id="GFX99",
|
||||
instantiation=False,
|
||||
filament_vendor="CV",
|
||||
filament_type="PLA"))
|
||||
self.t.write_preset("VendorA", preset("CNEW @P1", inherits="CNEW @base",
|
||||
compatible_printers=["P1"]))
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0, out)
|
||||
with open(self.t.snapshot, encoding="utf-8") as f:
|
||||
self.assertIn("GFX99", json.load(f)["ids"])
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn('is not a minted "OF" id', out)
|
||||
|
||||
def test_dry_run_reports_without_writing(self):
|
||||
self.t.write_preset("VendorA", preset("ANEW @P2", inherits="APLA @base",
|
||||
compatible_printers=["P2"]))
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
before = f.read()
|
||||
rc, out = self.t.update_snapshot(dry_run=True)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("would be rewritten", out)
|
||||
self.assertIn("claims added : 1", out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), before)
|
||||
# the real run writes exactly what the dry run reported
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("snapshot written", out)
|
||||
snap = load_json_file(self.t.snapshot)
|
||||
self.assertEqual(snap["ids"]["AX01"]["filaments"],
|
||||
["VendorA/ANEW", "VendorA/APLA"])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --generate: one rule for inserts and rewrites alike
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1026,7 +840,7 @@ class TestAssign(OfCleanTreeCase):
|
||||
def test_parent_of_another_filament_never_receives_the_key(self):
|
||||
# Members whose id-less parent belongs to another filament (here one
|
||||
# parent shared by two filaments) carry the key themselves: the
|
||||
# parent's own triple would mint a different id (check 3).
|
||||
# parent's own triple would mint a different id (check 2).
|
||||
self.t.write_preset("VendorA", preset("shared_base", instantiation=False,
|
||||
filament_vendor="SV",
|
||||
filament_type="PLA"))
|
||||
@@ -1044,8 +858,6 @@ class TestAssign(OfCleanTreeCase):
|
||||
parent = load_json_file(self.t.preset_path("VendorA", "shared_base"))
|
||||
self.assertNotIn("filament_id", parent)
|
||||
# ... and the tree they leave behind passes the identity check.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
@@ -1538,33 +1350,10 @@ class TestCli(unittest.TestCase):
|
||||
rc = afi.main([])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("usage:", buf.getvalue())
|
||||
for command in ("check", "generate-id", "normalize", "trim", "update-index",
|
||||
"update-snapshot"):
|
||||
for command in ("check", "generate-id", "normalize", "trim", "update-index"):
|
||||
self.assertIn(command, buf.getvalue())
|
||||
self.assertEqual(self.t.bytes_map(), before)
|
||||
|
||||
def test_another_tree_needs_its_own_snapshot(self):
|
||||
# --profiles retargets the tree, but the sanctioned state of that tree
|
||||
# is not the repo snapshot: checking against it is meaningless and
|
||||
# re-recording into it would overwrite the tracked file.
|
||||
with open(afi.SNAPSHOT_PATH, "rb") as f:
|
||||
repo_snapshot = f.read()
|
||||
for command in ("check", "update-snapshot"):
|
||||
with self.assertRaises(SystemExit) as caught:
|
||||
with contextlib.redirect_stderr(io.StringIO()):
|
||||
afi.main([command, "--profiles", self.t.profiles])
|
||||
self.assertEqual(caught.exception.code, 2, command)
|
||||
with open(afi.SNAPSHOT_PATH, "rb") as f:
|
||||
self.assertEqual(f.read(), repo_snapshot)
|
||||
# Named explicitly, both commands run against that tree.
|
||||
rc, out = self.t.cli("update-snapshot")
|
||||
self.assertEqual(rc, 0, out)
|
||||
# generate-id never reads the snapshot, so it keeps working without one.
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
rc = afi.main(["generate-id", "--dry-run", "--profiles", self.t.profiles])
|
||||
self.assertEqual(rc, 0, buf.getvalue())
|
||||
|
||||
def test_filament_id_and_setting_id_together_are_rejected(self):
|
||||
# Each flag's help promises it skips the other kind, so the pair cannot
|
||||
# quietly mean "both".
|
||||
@@ -1667,8 +1456,8 @@ class TestCli(unittest.TestCase):
|
||||
"setting_id", load_json_file(self.t.preset_path("VendorA", name)))
|
||||
|
||||
def test_check_returns_1_on_errors(self):
|
||||
# What CI keys off: check exits nonzero when the tree does not match the
|
||||
# snapshot it is validated against.
|
||||
# What CI keys off: check exits nonzero when the tree breaks a rule, here
|
||||
# the baseline's ids that are not minted.
|
||||
before = self.t.bytes_map()
|
||||
rc, out = self.t.cli("check")
|
||||
self.assertEqual(rc, 1)
|
||||
@@ -1695,12 +1484,12 @@ class TestCli(unittest.TestCase):
|
||||
["--check"], # the pre-subcommand flag
|
||||
["--update-snapshot"], # the pre-subcommand flag
|
||||
["nonsense"], # not a command
|
||||
["update-snapshot"], # removed command
|
||||
["generate-id", "--filament-id", "--setting-id"],
|
||||
["generate-id", "--snapshot", "x"], # check's option
|
||||
["check", "--materials"], # removed flag
|
||||
["check", "--obsolete-keys"], # removed flag
|
||||
["check", "--snapshot", "x"], # removed flag
|
||||
["check", "--filament-id"], # generate-id's option
|
||||
["normalize", "--snapshot", "x"], # not a snapshot command
|
||||
["normalize", "--profile-type", "nozzle"]): # not a profile type
|
||||
with self.subTest(argv=argv):
|
||||
with self.assertRaises(SystemExit) as cm, \
|
||||
@@ -1716,7 +1505,7 @@ class TestCli(unittest.TestCase):
|
||||
|
||||
@unittest.skipUnless(os.path.isdir(REAL_PROFILES), "resources/profiles not present")
|
||||
class TestRealTree(unittest.TestCase):
|
||||
def test_shipped_snapshot_matches_tree(self):
|
||||
def test_shipped_filament_ids_pass(self):
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
errors = afi.check_filament_ids(REAL_PROFILES)
|
||||
@@ -1778,10 +1567,10 @@ class TestReviewFixes(OfCleanTreeCase):
|
||||
self.assertIn(path, str(caught.exception))
|
||||
self.assertIn("test edit", str(caught.exception))
|
||||
|
||||
def test_check3_skips_of_id_inherited_from_other_vendor(self):
|
||||
def test_check2_accepts_an_of_id_inherited_from_another_vendor(self):
|
||||
# An OFL filament carries its own minted OF id and a vendor tunes it
|
||||
# correctly (same base name, non-empty printers). The new claim must
|
||||
# trip only the snapshot gate, never mint conformance.
|
||||
# correctly (same base name, non-empty printers): the id it inherits is
|
||||
# the mint of its own triple.
|
||||
fid = afi.generate_filament_id("Generic", "PLA", "Generic PLA Matte")
|
||||
self.t.write_preset(OFL, preset("Generic PLA Matte @base", filament_id=fid,
|
||||
instantiation=False,
|
||||
@@ -1790,22 +1579,13 @@ class TestReviewFixes(OfCleanTreeCase):
|
||||
self.t.write_preset(OFL, preset("Generic PLA Matte @System",
|
||||
inherits="Generic PLA Matte @base",
|
||||
compatible_printers=[]))
|
||||
rc, _ = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
self.t.write_preset("VendorA", preset("Generic PLA Matte @P1",
|
||||
inherits="Generic PLA Matte @System",
|
||||
compatible_printers=["P1 0.4 nozzle"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertNotIn("does not match the mint", out)
|
||||
self.assertIn("not sanctioned", out)
|
||||
self.assertEqual(errors, 1, out)
|
||||
# After sanctioning the claim the tree is fully green again.
|
||||
rc, _ = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
def test_check3c_prints_expected_mint(self):
|
||||
def test_check2c_prints_expected_mint(self):
|
||||
self.t.write_preset("VendorA", preset("Orphan PLA @P1",
|
||||
compatible_printers=["P1 0.4 nozzle"],
|
||||
filament_vendor="OV",
|
||||
|
||||
@@ -527,9 +527,7 @@ class TestCheck(TreeCase):
|
||||
# `check`, now that the per-vendor pass no longer skips it.
|
||||
self.t.write(apt.OFL, "filament/Stray.json",
|
||||
{"type": "filament", "name": "Stray"})
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn(f"{apt.OFL}/filament/Stray.json: no {apt.OFL}.json list "
|
||||
f"references it", out)
|
||||
@@ -598,9 +596,7 @@ class TestCheck(TreeCase):
|
||||
self.t.write("V", "filament/A.json", {
|
||||
"type": "filament", "name": "A", "silent_mode": "0"})
|
||||
self.run_command("update-index")
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out) # normalization also rejects the obsolete key
|
||||
self.assertIn("Obsolete key: 'silent_mode' found in V/filament/A.json", out)
|
||||
self.assertIn("Files with warnings : 1", out)
|
||||
@@ -623,9 +619,7 @@ class TestCheck(TreeCase):
|
||||
"type": "machine", "name": "M 0.4 nozzle",
|
||||
"default_filament_profile": ["A", "Nope"]})
|
||||
self.t.index("V", "machine", "M 0.4 nozzle", "machine/M.json")
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn("Missing filament profile: 'Nope'", out)
|
||||
|
||||
@@ -635,9 +629,7 @@ class TestCheck(TreeCase):
|
||||
self.bundle()
|
||||
for sub in apt.PROFILE_SUBDIRS:
|
||||
os.makedirs(os.path.join(self.t.profiles, apt.USER_DIR, "default", sub))
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
_rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
_rc, out = self.run_command("check")
|
||||
self.assertIn("Checked vendors : 1", out)
|
||||
self.assertNotIn("user", out)
|
||||
|
||||
@@ -743,9 +735,7 @@ class TestCheck(TreeCase):
|
||||
self.t.write("V", f"filament/Stray{n}.json",
|
||||
{"type": "filament", "name": f"Stray{n}"})
|
||||
self.t.write("V", "filament/NoType.json", {"name": "NoType"})
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertEqual(out.count("update-index\" to add them"), 1, out)
|
||||
self.assertEqual(out.count("or delete them"), 1, out)
|
||||
@@ -797,11 +787,6 @@ class TestNormalized(TreeCase):
|
||||
errors, gaps = apt.check_normalized(self.t.profiles, vendor)
|
||||
return errors, gaps, buf.getvalue()
|
||||
|
||||
def snapshot(self):
|
||||
path = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", path)
|
||||
return path
|
||||
|
||||
def test_a_bundle_the_two_commands_just_wrote_reports_nothing(self):
|
||||
self.t.write("V", "filament/A.json", {"type": "filament", "name": "A"})
|
||||
self.t.write("V", "process/B.json", {"type": "process", "name": "B"})
|
||||
@@ -862,7 +847,7 @@ class TestNormalized(TreeCase):
|
||||
# library included.
|
||||
self.t.write(apt.OFL, "filament/A.json",
|
||||
{"type": "filament", "name": "A", "version": "01.00.00.00"})
|
||||
rc, out = self.run_command("check", "--snapshot", self.snapshot())
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn(f"{apt.OFL}/filament/A.json: normalize would remove version", out)
|
||||
|
||||
@@ -872,7 +857,7 @@ class TestNormalized(TreeCase):
|
||||
{"type": "filament", "name": f"A{n}",
|
||||
"version": "01.00.00.00"})
|
||||
self.t.write("W", "filament/B.json", {"type": "filament", "name": "B"})
|
||||
rc, out = self.run_command("check", "--snapshot", self.snapshot())
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn("3 profile file(s) above are not what", out)
|
||||
self.assertEqual(out.count('normalize" writes: run it and commit'), 1, out)
|
||||
@@ -898,8 +883,7 @@ class TestDispatch(TreeCase):
|
||||
def test_an_option_belongs_to_one_command_only(self):
|
||||
for argv in (["trim", "--force"],
|
||||
["update-index", "--filament-id"],
|
||||
["check", "--profile-type", "filament"],
|
||||
["update-snapshot", "--vendor", "V"]):
|
||||
["check", "--profile-type", "filament"]):
|
||||
with self.subTest(argv=argv):
|
||||
with self.assertRaises(SystemExit) as cm, \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
|
||||
Reference in New Issue
Block a user