Content-address Bambu filament ids and check profiles against the catalog map

Every vendor's filament_id declarations, Bambu's own bundle included, are now
minted and checked the same way: the GF* catalog space is reserved but
ownerless, format is validated unconditionally with no snapshot or BBL
exemption, and both --remint and the default assign pass treat a non-OF
declaration as one needing a fresh mint (so a future BambuStudio sync
self-heals instead of needing a manual pass). A new check validates
resources/printers/bambu_filament_ids.json against the tree it describes:
that it parses, carries its header, keys only OF ids, maps each Bambu id once,
and agrees with the tree on every product it shares. The redundant BBL/OFL
carve-out in the profile checker's length check is dropped too, so it runs
the same way for every vendor.

The BBL bundle itself hasn't been touched yet and still declares its old GF
ids, so TestRealTree.test_shipped_snapshot_matches_tree is expected to fail
here (209 declarations flagged) until the next commit re-mints the bundle
onto OF ids.
This commit is contained in:
SoftFever
2026-09-04 14:25:00 +08:00
parent 25e020f5c9
commit 40cc3340b1
3 changed files with 297 additions and 92 deletions

View File

@@ -118,10 +118,11 @@ class SyntheticTree:
allow_shared_catalog=allow_shared_catalog)
return rc, buf.getvalue()
def check(self):
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)
errors = afi.check_filament_ids(self.profiles, self.snapshot, **kwargs)
return errors, buf.getvalue()
def assign(self):
@@ -142,17 +143,24 @@ class SyntheticTree:
dropped, errors = afi.drop_redundant_ids(vendor, self.profiles)
return dropped, errors, buf.getvalue()
def make_clean_tree():
"""Baseline tree: OFL base+generic, a vendor family, a clean tuned generic."""
def make_clean_tree(apla_id="AX01", generic_id="OGFL99"):
"""Baseline tree: OFL base+generic, a vendor family, a clean tuned generic.
apla_id/generic_id default to arbitrary non-OF placeholders (grandfathered
into the snapshot below) since most tests only need "already assigned,
don't touch". TestAssign passes real OF-format ids instead: assign_missing_ids
now treats a non-OF declaration as missing and remints it, so a non-OF
baseline would no longer be a no-op there.
"""
t = SyntheticTree()
t.add_vendor(OFL, [
preset("fdm_pla", filament_id="OGFL99", instantiation=False,
preset("fdm_pla", filament_id=generic_id, instantiation=False,
filament_vendor="Generic", filament_type="PLA"),
preset("Generic PLA @System", inherits="fdm_pla",
compatible_printers=[]),
])
t.add_vendor("VendorA", [
preset("APLA @base", filament_id="AX01", instantiation=False,
preset("APLA @base", filament_id=apla_id, instantiation=False,
filament_vendor="AVendor", filament_type="PLA"),
preset("APLA @P1", inherits="APLA @base",
compatible_printers=["P1 0.4 nozzle"]),
@@ -171,6 +179,21 @@ class SyntheticTreeCase(unittest.TestCase):
self.addCleanup(self.t.cleanup)
class OfCleanTreeCase(unittest.TestCase):
"""Like SyntheticTreeCase, but the baseline family/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 or drop (TestRemint, TestDropRedundantIds, TestUpdateSnapshot) keep
using SyntheticTreeCase instead.
"""
def setUp(self):
self.t = make_clean_tree(
apla_id=afi.generate_filament_id("AVendor", "PLA", "APLA"),
generic_id=afi.generate_filament_id("Generic", "PLA", "fdm_pla"))
self.addCleanup(self.t.cleanup)
# ---------------------------------------------------------------------------
# mint
# ---------------------------------------------------------------------------
@@ -376,12 +399,13 @@ class TestTripleResolution(unittest.TestCase):
# ---------------------------------------------------------------------------
# reserved namespaces / islands
# reserved namespaces
# ---------------------------------------------------------------------------
class TestReservedSpaces(unittest.TestCase):
def test_owners(self):
self.assertEqual(afi.reserved_space_owner("GFL99"), (True, "BBL"))
# Bambu AMS/RFID catalog: reserved, but no vendor (not even BBL) may declare it
self.assertEqual(afi.reserved_space_owner("GFL99"), (True, None))
# Qidi device protocol: 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))
@@ -390,20 +414,15 @@ class TestReservedSpaces(unittest.TestCase):
self.assertEqual(afi.reserved_space_owner("OF5CgdDq"), (False, None))
self.assertEqual(afi.reserved_space_owner("P1234abcd"), (False, None)) # 8 hex chars: not the user space
def test_island_declarations(self):
self.assertTrue(afi.is_island_declaration("BBL", "GFL99"))
self.assertTrue(afi.is_island_declaration("BBL", "OF5CgdDq"))
# Qidi presets mint like any other vendor's:
self.assertFalse(afi.is_island_declaration("Qidi", "QD_X4_PLA"))
self.assertFalse(afi.is_island_declaration("Qidi", "OF5CgdDq"))
self.assertFalse(afi.is_island_declaration("VendorA", "GFL99"))
def test_gf_is_reserved_and_ownerless(self):
self.assertEqual(afi.reserved_space_owner("GFA00"), (True, None))
# ---------------------------------------------------------------------------
# checks on synthetic trees
# ---------------------------------------------------------------------------
class TestChecks(SyntheticTreeCase):
class TestChecks(OfCleanTreeCase):
def test_clean_tree_is_silent(self):
errors, out = self.t.check()
self.assertEqual(errors, 0, out)
@@ -418,7 +437,7 @@ class TestChecks(SyntheticTreeCase):
compatible_printers=["P1"]))
errors, out = self.t.check()
self.assertGreater(errors, 0)
self.assertIn("neither grandfathered in the snapshot", out)
self.assertIn('is not a minted "OF" id', out)
self.assertIn("BOGUS_9", out)
def test_check2_new_claim_needs_snapshot_update(self):
@@ -437,7 +456,8 @@ class TestChecks(SyntheticTreeCase):
self.assertIn('"VendorA/APLA"', out)
def test_check2_triple_change_needs_snapshot_update(self):
self.t.write_preset("VendorA", preset("APLA @base", filament_id="AX01",
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
self.t.write_preset("VendorA", preset("APLA @base", filament_id=apla_id,
instantiation=False,
filament_vendor="AVendor",
filament_type="PETG"),
@@ -542,7 +562,7 @@ class TestChecks(SyntheticTreeCase):
self.assertEqual(errors, 0, out)
def test_check5_reserved_namespace_claims(self):
for fid, marker in [("GFX99", "owned by BBL"),
for fid, marker in [("GFX99", "Bambu AMS/RFID catalog"),
("QD_X_PLA", "composed by the device"),
("P1a2b3c4", "user-custom"),
("null", "user-custom")]:
@@ -560,7 +580,10 @@ class TestChecks(SyntheticTreeCase):
self.assertIn(marker, out)
def test_check6a_instantiated_preset_with_own_key(self):
self.t.write_preset("VendorA", preset("APLA @P1", filament_id="AX01",
# Same value the child would already resolve through inherits: this
# isolates check 6a (own key present) from check 6b (value drift).
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
self.t.write_preset("VendorA", preset("APLA @P1", filament_id=apla_id,
inherits="APLA @base",
compatible_printers=["P1 0.4 nozzle"]),
register=False)
@@ -569,13 +592,16 @@ class TestChecks(SyntheticTreeCase):
self.assertIn("declares its own filament_id key", out)
def test_check6b_declared_vs_inherited_drift(self):
self.t.write_preset("VendorA", preset("APLA @P1", filament_id="AX02",
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
drifted = afi.generate_filament_id("AVendor", "PLA", "APLA", salt=1)
self.t.write_preset("VendorA", preset("APLA @P1", filament_id=drifted,
inherits="APLA @base",
compatible_printers=["P1 0.4 nozzle"]),
register=False)
errors, out = self.t.check()
self.assertGreater(errors, 0)
self.assertIn('declares filament_id "AX02" but its inherits chain resolves "AX01"', out)
self.assertIn(f'declares filament_id "{drifted}" but its inherits chain resolves '
f'"{apla_id}"', out)
def test_check6c_unresolvable_instantiated_filament(self):
self.t.write_preset("VendorA", preset("DNEW @P1", compatible_printers=["P1"]))
@@ -591,7 +617,7 @@ class TestChecks(SyntheticTreeCase):
self.assertIn("snapshot not found", out)
class TestCheck7(SyntheticTreeCase):
class TestCheck7(OfCleanTreeCase):
def test_7a_empty_vendor_is_hard_error(self):
fid = afi.generate_filament_id("", "PLA", "NVPLA")
self.t.write_preset("VendorA", preset("NVPLA @base", filament_id=fid,
@@ -650,6 +676,43 @@ class TestCheck7(SyntheticTreeCase):
self.assertIn('"APLA"', out)
class TestCheck8(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")
return path
def test_row_triple_must_match_tree(self):
fid = afi.generate_filament_id("V", "PLA", "Foo")
self.t.write_preset("VendorA", preset("Foo @base", filament_id=fid,
instantiation=False,
filament_vendor="V", filament_type="PLA"))
self.t.write_preset("VendorA", preset("Foo @P1", inherits="Foo @base",
compatible_printers=["P1"]))
map_path = self._write_map(
{fid: {"bambu_id": "GFZ00", "vendor": "V", "type": "PLA", "name": "Bar"}})
errors, out = self.t.check(map_path)
self.assertGreater(errors, 0)
self.assertIn("regenerate the map", out)
def test_duplicate_bambu_id_is_an_error(self):
map_path = self._write_map({
"OFaaaaaa": {"bambu_id": "GFZ00", "vendor": "V", "type": "PLA", "name": "Foo"},
"OFbbbbbb": {"bambu_id": "GFZ00", "vendor": "V", "type": "PETG", "name": "Bar"},
})
errors, out = self.t.check(map_path)
self.assertGreater(errors, 0)
self.assertIn("GFZ00", out)
def test_row_for_unshipped_product_is_fine(self):
map_path = self._write_map({
"OFcccccc": {"bambu_id": "GFZ99", "vendor": "Nobody", "type": "PLA",
"name": "Ships Nothing"},
})
errors, out = self.t.check(map_path)
self.assertEqual(errors, 0, out)
# ---------------------------------------------------------------------------
# --update-snapshot
# ---------------------------------------------------------------------------
@@ -698,7 +761,7 @@ class TestUpdateSnapshot(SyntheticTreeCase):
# default run: mint + insert
# ---------------------------------------------------------------------------
class TestAssign(SyntheticTreeCase):
class TestAssign(OfCleanTreeCase):
def test_noop_on_fully_idded_tree(self):
changed, errors, out = self.t.assign()
self.assertEqual((changed, errors), (0, 0))
@@ -771,6 +834,56 @@ class TestAssign(SyntheticTreeCase):
self.assertGreater(errors, 0)
self.assertIn("shared with famil", out)
def test_non_of_declaration_is_treated_as_missing(self):
# A declaration that isn't OF-format (e.g. a vendor bundle synced from
# an upstream catalog, like BBL's GF ids) is reminted like a missing
# family, not left alone.
self.t.write_preset("VendorA", preset("Synced PLA @base", filament_id="GFZZ00",
instantiation=False,
filament_vendor="ZV", filament_type="PLA"))
self.t.write_preset("VendorA", preset("Synced PLA @P1",
inherits="Synced PLA @base",
compatible_printers=["P1"]))
changed, errors, out = self.t.assign()
self.assertEqual(errors, 0, out)
self.assertEqual(changed, 1)
path = self.t.preset_path("VendorA", "Synced PLA @base")
root = load_json_file(path)
want = afi.generate_filament_id("ZV", "PLA", "Synced PLA")
self.assertEqual(root["filament_id"], want)
# the value was replaced in place, not appended as a second key
raw = open(path, encoding="utf-8").read()
self.assertEqual(raw.count('"filament_id"'), 1)
# idempotent: the id is OF-format now, so a second run is a no-op
changed, errors, _out = self.t.assign()
self.assertEqual((changed, errors), (0, 0))
def test_non_of_multi_root_family_converges_on_one_id(self):
# Two per-printer roots of one product (same triple), both carrying
# the SAME non-OF id — the shape a synced vendor bundle ships (e.g.
# BambuStudio's own per-printer @base files). They must converge on
# one freshly minted id, not split into two.
self.t.write_preset("VendorA", preset("Synced ABS @P1base", filament_id="GFSYNC0",
instantiation=False,
filament_vendor="ZV", filament_type="ABS"))
self.t.write_preset("VendorA", preset("Synced ABS @P2base", filament_id="GFSYNC0",
instantiation=False,
filament_vendor="ZV", filament_type="ABS"))
self.t.write_preset("VendorA", preset("Synced ABS @P1",
inherits="Synced ABS @P1base",
compatible_printers=["P1"]))
self.t.write_preset("VendorA", preset("Synced ABS @P2",
inherits="Synced ABS @P2base",
compatible_printers=["P2"]))
changed, errors, out = self.t.assign()
self.assertEqual(errors, 0, out)
self.assertEqual(changed, 2)
want = afi.generate_filament_id("ZV", "ABS", "Synced ABS")
b1 = load_json_file(self.t.preset_path("VendorA", "Synced ABS @P1base"))
b2 = load_json_file(self.t.preset_path("VendorA", "Synced ABS @P2base"))
self.assertEqual(b1["filament_id"], want)
self.assertEqual(b2["filament_id"], want)
# ---------------------------------------------------------------------------
# byte-preserving profile edits
@@ -962,11 +1075,19 @@ class TestRemint(SyntheticTreeCase):
self.assertEqual(root["filament_id"],
afi.generate_filament_id(*self.TRIPLE, salt=1))
def test_bbl_is_forbidden(self):
def test_bbl_is_reminted_like_any_vendor(self):
self.t.add_vendor("BBL", [
preset("Bambu ABS @base", filament_id="GFB00", instantiation=False,
filament_vendor="Bambu Lab", filament_type="ABS"),
preset("Bambu ABS @P1", inherits="Bambu ABS @base",
compatible_printers=["P1"]),
])
changed, errors, out = self.t.remint(["BBL"])
self.assertEqual(changed, 0)
self.assertGreater(errors, 0)
self.assertIn("forbidden", out)
self.assertEqual(errors, 0, out)
self.assertEqual(changed, 1)
root = load_json_file(self.t.preset_path("BBL", "Bambu ABS @base"))
self.assertEqual(root["filament_id"],
afi.generate_filament_id("Bambu Lab", "ABS", "Bambu ABS"))
# ---------------------------------------------------------------------------
@@ -1044,7 +1165,7 @@ class TestRealTree(unittest.TestCase):
# review-fix regressions
# ---------------------------------------------------------------------------
class TestReviewFixes(SyntheticTreeCase):
class TestReviewFixes(OfCleanTreeCase):
def test_check3_skips_of_id_inherited_from_other_vendor(self):
# An OFL family carries its own minted OF id and a vendor tunes it
# correctly (same base name, non-empty printers). The new claim must