mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-29 21:07:11 +00:00
filament_id v4.2a: dissolve the QD_* island in the tooling (plan v4 SS3)
- is_island_declaration: only BBL remains an island; Qidi QD_* declarations now enter the triple bookkeeping (checks 3 and 8, --remint's domain). - reserved_space_owner: QD_* stays reserved but ownerless -- no vendor may declare it, --update-snapshot refuses new QD_* ids outright, and vanished QD_* ids take the retired-with-successor path instead of the island release-with-hint path. New reserved_space_desc() names the space in check 6 / sanction-gate messages. - Check 1 drops the vendor-Qidi QD_* format exemption; --add-hint now refuses QD_* keys (island space is GF* only); docstrings updated. - Snapshot regenerated: the 204 QD_* declarer triples are now recorded (declared triples 1113 -> 1317); ids/claims unchanged. - Tests updated for the flip; new (skipped until v4.2b) lockstep test asserting the QidiPrinterAgent fallback QD_* literals stay retired ledger keys chaining to live ids. Tree state is untouched: presets still declare their QD_* ids, all grandfathered by the snapshot. --check exit 0; 117 script tests OK.
This commit is contained in:
@@ -26,7 +26,11 @@ Policy (companion to assign_vendor_setting_ids.py; see filament_id_plan_v3.md):
|
|||||||
resolving.
|
resolving.
|
||||||
* Reserved id spaces that are never minted into or altered:
|
* Reserved id spaces that are never minted into or altered:
|
||||||
- GF* Bambu AMS/RFID catalog (vendor BBL untouchable)
|
- GF* Bambu AMS/RFID catalog (vendor BBL untouchable)
|
||||||
- QD_* Qidi device protocol
|
- QD_* Qidi device protocol (dissolved island, plan v4:
|
||||||
|
every shipped QD_* id is retired in the ledger and
|
||||||
|
the QidiPrinterAgent translates device-composed
|
||||||
|
QD_* ids through the succession walk; no preset
|
||||||
|
may ever declare one again)
|
||||||
- P + 7 hex chars (case-insensitive) and the literal "null"
|
- P + 7 hex chars (case-insensitive) and the literal "null"
|
||||||
user-custom presets (CreatePresetsDialog.cpp)
|
user-custom presets (CreatePresetsDialog.cpp)
|
||||||
- every already-shipped id, grandfathered via scripts/filament_id_snapshot.json
|
- every already-shipped id, grandfathered via scripts/filament_id_snapshot.json
|
||||||
@@ -40,7 +44,7 @@ Policy (companion to assign_vendor_setting_ids.py; see filament_id_plan_v3.md):
|
|||||||
rule over the vanished id's old claims — and the runtime follows those
|
rule over the vanished id's old claims — and the runtime follows those
|
||||||
chains (plus cross-island "hints" for ids Orca cannot retire, e.g. GF*)
|
chains (plus cross-island "hints" for ids Orca cannot retire, e.g. GF*)
|
||||||
on resolution miss. A retired id may never be used again for anything.
|
on resolution miss. A retired id may never be used again for anything.
|
||||||
Vanished ids in a FOREIGN island's space (GF*/QD_*) are released with a
|
Vanished ids in a FOREIGN island's space (GF*) are released with a
|
||||||
hint instead of retired: the island's catalog owns them and may
|
hint instead of retired: the island's catalog owns them and may
|
||||||
legitimately (re)ship them.
|
legitimately (re)ship them.
|
||||||
|
|
||||||
@@ -325,8 +329,9 @@ def resolve_triple(name, filaments, ofl_filaments):
|
|||||||
|
|
||||||
|
|
||||||
def is_island_declaration(vendor, fid):
|
def is_island_declaration(vendor, fid):
|
||||||
"""Declarations frozen outside the OF mint domain: all of BBL, QD_* in Qidi."""
|
"""Declarations frozen outside the OF mint domain: all of BBL (the QD_*
|
||||||
return vendor == "BBL" or (vendor == "Qidi" and fid.startswith("QD_"))
|
island was dissolved by plan v4 — Qidi declarations mint like any other)."""
|
||||||
|
return vendor == "BBL"
|
||||||
|
|
||||||
|
|
||||||
def analyze_tree(profiles_dir):
|
def analyze_tree(profiles_dir):
|
||||||
@@ -598,12 +603,21 @@ def reserved_space_owner(fid):
|
|||||||
if fid.startswith("GF"):
|
if fid.startswith("GF"):
|
||||||
return True, "BBL"
|
return True, "BBL"
|
||||||
if fid.startswith("QD_"):
|
if fid.startswith("QD_"):
|
||||||
return True, "Qidi"
|
return True, None # dissolved Qidi device-protocol space: NO vendor may declare it
|
||||||
if USER_CUSTOM_ID_RE.match(fid) or fid == "null":
|
if USER_CUSTOM_ID_RE.match(fid) or fid == "null":
|
||||||
return True, None # user-custom space: no system vendor may own it
|
return True, None # user-custom space: no system vendor may own it
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
|
def reserved_space_desc(fid, owner):
|
||||||
|
"""Human description of a reserved space for error messages."""
|
||||||
|
if owner:
|
||||||
|
return f"owned by {owner}"
|
||||||
|
if fid.startswith("QD_"):
|
||||||
|
return "Qidi device protocol; dissolved island — ids are retired, never declarable"
|
||||||
|
return "reserved for user-custom presets"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Checks (imported and called tree-wide by orca_extra_profile_check.py)
|
# Checks (imported and called tree-wide by orca_extra_profile_check.py)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -613,8 +627,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
"""Validate filament_id state across every vendor. Returns the error count.
|
"""Validate filament_id state across every vendor. Returns the error count.
|
||||||
|
|
||||||
1. Format: every id occurring in the tree (declared or effective) must be in
|
1. Format: every id occurring in the tree (declared or effective) must be in
|
||||||
the snapshot, or match ^OF[0-9A-Za-z]{6}$, or belong to vendor BBL, or be
|
the snapshot, or match ^OF[0-9A-Za-z]{6}$, or belong to vendor BBL.
|
||||||
a QD_* id within vendor Qidi.
|
|
||||||
2. Snapshot equality, both directions: the tree-derived id->families multimap
|
2. Snapshot equality, both directions: the tree-derived id->families multimap
|
||||||
AND the id->triples map of the declarers must equal the snapshot exactly
|
AND the id->triples map of the declarers must equal the snapshot exactly
|
||||||
(the snapshot diff is the maintainer gate).
|
(the snapshot diff is the maintainer gate).
|
||||||
@@ -625,8 +638,8 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
5. Alias hygiene: a vendor preset riding an OFL family must keep the OFL
|
5. Alias hygiene: a vendor preset riding an OFL family must keep the OFL
|
||||||
base name, claim printers via non-empty compatible_printers, and declare
|
base name, claim printers via non-empty compatible_printers, and declare
|
||||||
no filament_id key of its own.
|
no filament_id key of its own.
|
||||||
6. Reserved namespaces (GF*/QD_*/P-hex/"null") only for their owner vendors,
|
6. Reserved namespaces (GF* for BBL; QD_*/P-hex/"null" for nobody) must not
|
||||||
except claims grandfathered in the snapshot.
|
be claimed by other vendors, except claims grandfathered in the snapshot.
|
||||||
7. Structure ratchet: (a) no NEW instantiated preset carries its own
|
7. Structure ratchet: (a) no NEW instantiated preset carries its own
|
||||||
filament_id key; (b) no NEW declared-vs-inherited id drift; (c) every
|
filament_id key; (b) no NEW declared-vs-inherited id drift; (c) every
|
||||||
instantiated filament resolves an effective id (a hard load error in C++).
|
instantiated filament resolves an effective id (a hard load error in C++).
|
||||||
@@ -664,8 +677,6 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
continue
|
continue
|
||||||
if vendor == "BBL":
|
if vendor == "BBL":
|
||||||
continue
|
continue
|
||||||
if vendor == "Qidi" and fid.startswith("QD_"):
|
|
||||||
continue
|
|
||||||
print_error(
|
print_error(
|
||||||
f'filament_id "{fid}" ({vendor}) is neither grandfathered in the '
|
f'filament_id "{fid}" ({vendor}) is neither grandfathered in the '
|
||||||
f'snapshot nor a minted "OF" id; new family ids must come from '
|
f'snapshot nor a minted "OF" id; new family ids must come from '
|
||||||
@@ -765,7 +776,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
continue
|
continue
|
||||||
if claim in snap_ids.get(fid, []):
|
if claim in snap_ids.get(fid, []):
|
||||||
continue # grandfathered
|
continue # grandfathered
|
||||||
space = f"owned by {owner}" if owner else "reserved for user-custom presets"
|
space = reserved_space_desc(fid, owner)
|
||||||
print_error(
|
print_error(
|
||||||
f'filament_id "{fid}" of "{claim}" is in a reserved id space '
|
f'filament_id "{fid}" of "{claim}" is in a reserved id space '
|
||||||
f"({space}) and must not be claimed by system presets of other vendors")
|
f"({space}) and must not be claimed by system presets of other vendors")
|
||||||
@@ -861,7 +872,7 @@ def check_filament_ids(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
if island is None:
|
if island is None:
|
||||||
print_error(
|
print_error(
|
||||||
f'hint key "{key}" ({RETIRED_REL}) is not in an island id space '
|
f'hint key "{key}" ({RETIRED_REL}) is not in an island id space '
|
||||||
f"(GF*/QD_*); non-island ids are retired with a successor instead")
|
f"(GF*); non-island ids are retired with a successor instead")
|
||||||
errors += 1
|
errors += 1
|
||||||
continue
|
continue
|
||||||
if key in retired:
|
if key in retired:
|
||||||
@@ -902,7 +913,7 @@ def update_snapshot(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
|
|
||||||
Vanished ids gain a succession-ledger entry whose successor is picked by
|
Vanished ids gain a succession-ledger entry whose successor is picked by
|
||||||
the mode rule over their old claims (None when the family died heirless);
|
the mode rule over their old claims (None when the family died heirless);
|
||||||
vanished ids in a foreign island's space (GF*/QD_*) are released with a
|
vanished ids in a foreign island's space (GF*) are released with a
|
||||||
hint instead — the island catalog owns them, so they must stay mintable
|
hint instead — the island catalog owns them, so they must stay mintable
|
||||||
there and must never be blocked by check 4.
|
there and must never be blocked by check 4.
|
||||||
--forget-never-shipped lists ids to drop from lineage instead of retiring
|
--forget-never-shipped lists ids to drop from lineage instead of retiring
|
||||||
@@ -955,7 +966,7 @@ def update_snapshot(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
refusals.append((fid, f"{vendor}/(declared only)", owner))
|
refusals.append((fid, f"{vendor}/(declared only)", owner))
|
||||||
if refusals and not allow_shared_catalog:
|
if refusals and not allow_shared_catalog:
|
||||||
for fid, claim, owner in refusals:
|
for fid, claim, owner in refusals:
|
||||||
space = f"owned by {owner}" if owner else "reserved for user-custom presets"
|
space = reserved_space_desc(fid, owner)
|
||||||
print_error(
|
print_error(
|
||||||
f'refusing to sanction new claim "{claim}" on reserved-namespace id '
|
f'refusing to sanction new claim "{claim}" on reserved-namespace id '
|
||||||
f'"{fid}" ({space}); pass --allow-shared-catalog only for '
|
f'"{fid}" ({space}); pass --allow-shared-catalog only for '
|
||||||
@@ -977,7 +988,7 @@ def update_snapshot(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
|
|
||||||
# Retire ids that fully vanished from the tree (append-only ledger), with a
|
# Retire ids that fully vanished from the tree (append-only ledger), with a
|
||||||
# mode-rule successor so devices and user presets keep resolving. Vanished
|
# mode-rule successor so devices and user presets keep resolving. Vanished
|
||||||
# ids in a FOREIGN island's reserved space (GF*/QD_*) are never retired —
|
# ids in a FOREIGN island's reserved space (GF*) are never retired —
|
||||||
# the island's catalog still owns them and may legitimately (re)ship them —
|
# the island's catalog still owns them and may legitimately (re)ship them —
|
||||||
# they are RELEASED, with a succession hint so they keep resolving on miss.
|
# they are RELEASED, with a succession hint so they keep resolving on miss.
|
||||||
claim_ids = claim_effective_ids(analysis)
|
claim_ids = claim_effective_ids(analysis)
|
||||||
@@ -1073,7 +1084,7 @@ def update_snapshot(profiles_dir=PROFILES_DIR, snapshot_path=SNAPSHOT_PATH,
|
|||||||
def add_hints(pairs, profiles_dir=PROFILES_DIR, retired_path=RETIRED_PATH):
|
def add_hints(pairs, profiles_dir=PROFILES_DIR, retired_path=RETIRED_PATH):
|
||||||
"""--add-hint "OLD=NEW": forward an island-space id to a live id.
|
"""--add-hint "OLD=NEW": forward an island-space id to a live id.
|
||||||
|
|
||||||
OLD must sit in an island id space (GF*/QD_*), must not be retired, and —
|
OLD must sit in an island id space (GF*), must not be retired, and —
|
||||||
when live in the tree — may be declared only by island vendors (Orca cannot
|
when live in the tree — may be declared only by island vendors (Orca cannot
|
||||||
retire it). An absent OLD is fine: island catalogs own ids Orca never
|
retire it). An absent OLD is fine: island catalogs own ids Orca never
|
||||||
shipped or has released. NEW must be live in the tree. All pairs validate
|
shipped or has released. NEW must be live in the tree. All pairs validate
|
||||||
@@ -1103,7 +1114,7 @@ def add_hints(pairs, profiles_dir=PROFILES_DIR, retired_path=RETIRED_PATH):
|
|||||||
errors += 1
|
errors += 1
|
||||||
elif island is None:
|
elif island is None:
|
||||||
print_error(
|
print_error(
|
||||||
f'--add-hint: "{old}" is not in an island id space (GF*/QD_*); '
|
f'--add-hint: "{old}" is not in an island id space (GF*); '
|
||||||
f"non-island ids are retired with a successor by --update-snapshot")
|
f"non-island ids are retired with a successor by --update-snapshot")
|
||||||
errors += 1
|
errors += 1
|
||||||
elif outsiders:
|
elif outsiders:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import contextlib
|
|||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -400,7 +401,8 @@ class TestTripleResolution(unittest.TestCase):
|
|||||||
class TestReservedSpaces(unittest.TestCase):
|
class TestReservedSpaces(unittest.TestCase):
|
||||||
def test_owners(self):
|
def test_owners(self):
|
||||||
self.assertEqual(afi.reserved_space_owner("GFL99"), (True, "BBL"))
|
self.assertEqual(afi.reserved_space_owner("GFL99"), (True, "BBL"))
|
||||||
self.assertEqual(afi.reserved_space_owner("QD_X4_PLA"), (True, "Qidi"))
|
# dissolved island (plan v4): reserved, but no vendor may declare it
|
||||||
|
self.assertEqual(afi.reserved_space_owner("QD_X4_PLA"), (True, None))
|
||||||
self.assertEqual(afi.reserved_space_owner("P1234abc"), (True, None))
|
self.assertEqual(afi.reserved_space_owner("P1234abc"), (True, None))
|
||||||
self.assertEqual(afi.reserved_space_owner("pAbCdEf1"), (True, None)) # case-insensitive
|
self.assertEqual(afi.reserved_space_owner("pAbCdEf1"), (True, None)) # case-insensitive
|
||||||
self.assertEqual(afi.reserved_space_owner("null"), (True, None))
|
self.assertEqual(afi.reserved_space_owner("null"), (True, None))
|
||||||
@@ -410,7 +412,8 @@ class TestReservedSpaces(unittest.TestCase):
|
|||||||
def test_island_declarations(self):
|
def test_island_declarations(self):
|
||||||
self.assertTrue(afi.is_island_declaration("BBL", "GFL99"))
|
self.assertTrue(afi.is_island_declaration("BBL", "GFL99"))
|
||||||
self.assertTrue(afi.is_island_declaration("BBL", "OF5CgdDq"))
|
self.assertTrue(afi.is_island_declaration("BBL", "OF5CgdDq"))
|
||||||
self.assertTrue(afi.is_island_declaration("Qidi", "QD_X4_PLA"))
|
# dissolved island (plan v4): Qidi declarations mint like any other
|
||||||
|
self.assertFalse(afi.is_island_declaration("Qidi", "QD_X4_PLA"))
|
||||||
self.assertFalse(afi.is_island_declaration("Qidi", "OF5CgdDq"))
|
self.assertFalse(afi.is_island_declaration("Qidi", "OF5CgdDq"))
|
||||||
self.assertFalse(afi.is_island_declaration("VendorA", "GFL99"))
|
self.assertFalse(afi.is_island_declaration("VendorA", "GFL99"))
|
||||||
|
|
||||||
@@ -567,7 +570,7 @@ class TestChecks(SyntheticTreeCase):
|
|||||||
|
|
||||||
def test_check6_reserved_namespace_claims(self):
|
def test_check6_reserved_namespace_claims(self):
|
||||||
for fid, marker in [("GFX99", "owned by BBL"),
|
for fid, marker in [("GFX99", "owned by BBL"),
|
||||||
("QD_X_PLA", "owned by Qidi"),
|
("QD_X_PLA", "dissolved island"),
|
||||||
("P1a2b3c4", "user-custom"),
|
("P1a2b3c4", "user-custom"),
|
||||||
("null", "user-custom")]:
|
("null", "user-custom")]:
|
||||||
with self.subTest(fid=fid):
|
with self.subTest(fid=fid):
|
||||||
@@ -1478,6 +1481,31 @@ class TestRealTree(unittest.TestCase):
|
|||||||
self.assertEqual(analysis["missing_effective"], [])
|
self.assertEqual(analysis["missing_effective"], [])
|
||||||
self.assertEqual(analysis["read_errors"], [])
|
self.assertEqual(analysis["read_errors"], [])
|
||||||
|
|
||||||
|
# Code<->ledger lockstep for the Qidi box (plan v4 SS2): the QD_* literals
|
||||||
|
# QidiPrinterAgent::map_filament_type_to_setting_id returns must stay
|
||||||
|
# retired ledger keys whose chains end at a live preset id, because the
|
||||||
|
# agent resolves them through resolve_filament_id_succession() at runtime.
|
||||||
|
@unittest.skip("enable with plan v4.2: QD_* ids are not retired yet")
|
||||||
|
def test_qidi_agent_fallback_ids_resolve_through_ledger(self):
|
||||||
|
cpp = os.path.join(REPO_ROOT, "src", "slic3r", "Utils", "QidiPrinterAgent.cpp")
|
||||||
|
with open(cpp, encoding="utf-8") as f:
|
||||||
|
src = f.read()
|
||||||
|
m = re.search(
|
||||||
|
r"map_filament_type_to_setting_id\(const std::string& filament_type\)"
|
||||||
|
r"\s*\{(.*?)\n\}", src, re.DOTALL)
|
||||||
|
self.assertIsNotNone(m, "map_filament_type_to_setting_id not found in QidiPrinterAgent.cpp")
|
||||||
|
qd_ids = re.findall(r'return "(QD_[0-9_]+)";', m.group(1))
|
||||||
|
self.assertTrue(qd_ids, "no QD_* literals parsed from map_filament_type_to_setting_id")
|
||||||
|
ledger = afi.load_ledger(afi.RETIRED_PATH)
|
||||||
|
live = set(afi.analyze_tree(REAL_PROFILES)["ids"])
|
||||||
|
for fid in qd_ids:
|
||||||
|
self.assertIn(fid, ledger["retired"], f'"{fid}" must be a retired ledger key')
|
||||||
|
status, node = afi.follow_succession(
|
||||||
|
fid, set(), ledger["retired"], ledger["hints"], live)
|
||||||
|
self.assertEqual(
|
||||||
|
status, "live",
|
||||||
|
f'"{fid}" succession must end at a live id (got {status} at "{node}")')
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# review-fix regressions
|
# review-fix regressions
|
||||||
|
|||||||
Reference in New Issue
Block a user