v3.2: vendor-bundle re-mint under the product-triple rule

Every remaining legacy filament_id outside the BBL/QD_* islands re-derives
from its product triple (filament_vendor / filament_type / family name),
completing the content-addressed id model of filament_id_plan_v3.md:

- Relocation pre-step: 14 fdm_filament_* template forks (Cubicon x3,
  Prusa x6, RH3D x6 minus the pc fork already re-homed) stop declaring
  ids; the 16 presets that rode Prusa's forks now declare the id they
  already resolved (verified zero effective-id drift over all 5892
  instantiated presets).
- --drop-redundant-ids: 6 Custom/MyToolChanger generics drop copied GF
  ids and ride their OFL families.
- --remint over all 62 non-island vendors: 2647 declarations re-derived;
  identical products converge cross-bundle (showcase: PolyLite PLA is now
  OF5CgdDq in OrcaFilamentLibrary, Qidi, OrcaArena and Snapmaker).
- Prusament @XL completions surfaced by the relocation: filament_vendor
  ["Prusa Polymers"] on the 8 @XL declarers; Prusament PA-CF typed
  PA11-CF (the product is Prusament PA11CF) and PC-CF typed PC-CF
  (-CF family typed as base polymer); each family converges on one id.
- Succession: 331 ids retired with mode-rule successors, 355 never-shipped
  v1 mints forgotten with chain splicing, 151 GF-shaped ids released to
  the island space with hints, 27 curated BBL-generic -> OFL-generic
  hints (GFL99 -> OFDSrzZ8 class). New --retire "OLD=NEW" maintenance
  mode records lineage for OGFC99/OGFG99/OGFN99 (the shipped ids of OFL's
  Generic PC/PETG/PA in released versions, whose claims migrated in v3.1
  while Cubicon's inert fork declarations kept them alive).
- Retiring the P-hex system ids removes the last system ids from
  check_ams_filament_valid's destructive P-gate.
- AMS-ambiguity fixes surfaced by convergence (validator -f): 4 presets
  riding another family's id through inherits now declare their true
  family id (Elegoo Generic ASA-CF/PETG-CF, Snapmaker PolyLite Dual PLA /
  PolyLite J1 PLA); 9 Cubicon @base presets and Dremel Generic PLA, which
  duplicate their per-printer variants on the same printers, split onto
  the salt-1 iteration of their triple (sanctioned by the mint-conformance
  check; --remint now leaves salt-conformant declarations alone).

Gates: assign_filament_ids --check 0; orca_extra_profile_check 0;
116 python unit tests; config-equivalence over all 5892 instantiated
presets (byte-identical configs except the 14 sanctioned Prusament
vendor/type corrections; setting_id and compatible_printers unchanged;
per-family convergence and ledger conservation verified); profile
validator base/-f/-r(BBL)/-r(Qidi) all green; libslic3r_tests 48610
assertions; Moonraker OFL generic map check; custom-preset fixture
archives v1.9.0-v2.4.1.
This commit is contained in:
SoftFever
2026-07-04 21:09:29 +08:00
parent e3ac835cc3
commit f7c1b290fd
2663 changed files with 12019 additions and 12498 deletions

View File

@@ -60,6 +60,11 @@ Run from anywhere: python3 scripts/assign_filament_ids.py
drops listed never-shipped ids from lineage instead)
--add-hint "OLD=NEW"
record a cross-island succession hint (standalone)
--retire "OLD=NEW"
retire a vanished non-island shipped id with an explicit
successor (standalone; for lineage the claim vote can no
longer see because the claims migrated while another
declarer kept the id alive)
--remint VENDOR re-derive VENDOR's declared ids from their triples and
rewrite mismatches in place (repeatable)
--drop-redundant-ids VENDOR
@@ -1121,6 +1126,69 @@ def add_hints(pairs, profiles_dir=PROFILES_DIR, retired_path=RETIRED_PATH):
return 0
def retire_ids(pairs, profiles_dir=PROFILES_DIR, retired_path=RETIRED_PATH):
"""--retire "OLD=NEW": retire a vanished non-island id with an explicit successor.
For shipped ids whose lineage the claim vote can no longer see: the id
vanished from the tree in an earlier phase's shadow — another declarer kept
it alive while its claims migrated — so no --update-snapshot run ever saw
the id vanish while its claims could still vote. OLD must be absent from
the tree (declared or effective) and outside every reserved id space
(island ids forward via --add-hint; user-custom ids are never system ids),
and must not already be retired or hinted. NEW must be a live tree id.
Appends {"claims": [], "successor": NEW}. All pairs validate before
anything is written. Returns 0 on success.
"""
_utf8_console()
analysis = analyze_tree(profiles_dir)
errors = 0
for msg in analysis["read_errors"]:
print_error(msg)
errors += 1
ledger = load_ledger(retired_path)
live = set(analysis["ids"])
declared_anywhere = set()
for vids in analysis["declared_ids"].values():
declared_anywhere |= set(vids)
parsed = []
for pair in pairs:
if "=" not in pair:
print_error(f'--retire expects "OLD=NEW", got "{pair}"')
errors += 1
continue
old, new = pair.split("=", 1)
is_reserved, _island = reserved_space_owner(old)
if old in ledger["retired"]:
print_error(f'--retire: "{old}" is already retired')
errors += 1
elif old in ledger["hints"]:
print_error(f'--retire: "{old}" is a hint key; it already forwards')
errors += 1
elif is_reserved:
print_error(
f'--retire: "{old}" sits in a reserved id space; island ids '
f"forward via --add-hint, user-custom ids are never retired")
errors += 1
elif old in live or old in declared_anywhere:
print_error(
f'--retire: "{old}" still lives in the tree; re-mint or delete '
f"its declarers first (an id that vanishes normally gets its "
f"successor voted by --update-snapshot)")
errors += 1
elif new not in live:
print_error(f'--retire: successor "{new}" is not a live tree id')
errors += 1
else:
parsed.append((old, new))
if errors:
return 1
for old, new in parsed:
ledger["retired"][old] = {"claims": [], "successor": new}
print_info(f'retired: "{old}" -> "{new}"')
write_ledger(retired_path, {"retired": ledger["retired"], "hints": ledger["hints"]})
return 0
# ---------------------------------------------------------------------------
# Byte-preserving profile edits
# ---------------------------------------------------------------------------
@@ -1336,6 +1404,9 @@ def remint_vendors(vendor_list, profiles_dir=PROFILES_DIR, retired_path=RETIRED_
triple; rewrite mismatching declarations in place (byte-preserving). Never
touches the snapshot — run --update-snapshot afterwards and review the diff.
A declaration already equal to ANY salt iteration of its own triple is
mint-conformant (check 3) and left alone — deliberate salt splits (distinct
presets of one product kept apart for per-printer AMS matching) survive.
A candidate id is blocked when it is a retired key, a hints key, or occurs
in the tree with any triple set other than exactly {T}. Equal-triple reuse
is convergence: the same product must end up under the same id everywhere.
@@ -1393,6 +1464,13 @@ def remint_vendors(vendor_list, profiles_dir=PROFILES_DIR, retired_path=RETIRED_
if is_island_declaration(vendor, fid):
continue
scanned += 1
# Any salt iteration of the declarer's own triple is already
# mint-conformant (check 3) — leave it. This keeps deliberate salt
# splits (two presets of one product that must stay distinct for
# per-printer AMS matching, validator -f) stable across re-mints.
if fid in {generate_filament_id(*rec["triple"], salt=s)
for s in range(MAX_CHECK_SALT + 1)}:
continue
want = want_id(rec["triple"])
if fid == want:
continue
@@ -1481,6 +1559,10 @@ def main(argv=None):
help="record a cross-island succession hint (island-owned "
"live id -> live id) in the shipped ledger; standalone, "
"repeatable")
parser.add_argument("--retire", metavar='"OLD=NEW"', action="append",
help="retire a vanished non-island shipped id with an "
"explicit successor (for lineage the claim vote can "
"no longer see); standalone, repeatable")
parser.add_argument("--remint", metavar="VENDOR", action="append",
help="re-derive VENDOR's declared filament_ids from their "
"triples and rewrite mismatches in place; repeatable")
@@ -1507,10 +1589,16 @@ def main(argv=None):
return 0
if args.add_hint:
if args.update_snapshot or args.check or args.remint or args.drop_redundant_ids:
if (args.update_snapshot or args.check or args.remint
or args.drop_redundant_ids or args.retire):
parser.error("--add-hint runs standalone")
return add_hints(args.add_hint, args.profiles, RETIRED_PATH)
if args.retire:
if args.update_snapshot or args.check or args.remint or args.drop_redundant_ids:
parser.error("--retire runs standalone")
return retire_ids(args.retire, args.profiles, RETIRED_PATH)
if args.remint:
if args.update_snapshot or args.check or args.drop_redundant_ids:
parser.error("--remint cannot be combined with other modes")

File diff suppressed because it is too large Load Diff

View File

@@ -153,6 +153,12 @@ class SyntheticTree:
rc = afi.add_hints(pairs, self.profiles, self.retired)
return rc, buf.getvalue()
def retire(self, pairs):
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
rc = afi.retire_ids(pairs, self.profiles, self.retired)
return rc, buf.getvalue()
def make_clean_tree():
"""Baseline tree: OFL base+generic, a vendor family, a clean tuned generic."""
@@ -1049,6 +1055,65 @@ class TestAddHint(SyntheticTreeCase):
self.assertIn('expects "OLD=NEW"', out)
class TestRetire(SyntheticTreeCase):
def test_retires_vanished_id_with_successor(self):
rc, out = self.t.retire(["OFOLD001=AX01"])
self.assertEqual(rc, 0, out)
self.assertEqual(load_json_file(self.t.retired)["retired"]["OFOLD001"],
{"claims": [], "successor": "AX01"})
errors, out = self.t.check()
self.assertEqual(errors, 0, out)
def test_rejects_live_old(self):
rc, out = self.t.retire(["AX01=OGFL99"])
self.assertEqual(rc, 1)
self.assertIn("still lives in the tree", out)
self.assertEqual(afi.load_ledger(self.t.retired)["retired"], {})
def test_rejects_declaration_only_old(self):
# The inert-fork case: an id declared by a claim-less root still lives
# in the tree; the declaration must be deleted before --retire.
self.t.write_preset("VendorA", preset("ZOLD @base", filament_id="ZDECL01",
instantiation=False,
filament_vendor="ZV",
filament_type="PLA"))
rc, out = self.t.retire(["ZDECL01=AX01"])
self.assertEqual(rc, 1)
self.assertIn("still lives in the tree", out)
def test_rejects_island_space_old(self):
rc, out = self.t.retire(["GFXX99=AX01"])
self.assertEqual(rc, 1)
self.assertIn("reserved id space", out)
def test_rejects_user_space_old(self):
rc, out = self.t.retire(["P1234ABC=AX01"])
self.assertEqual(rc, 1)
self.assertIn("reserved id space", out)
def test_rejects_already_retired_old(self):
self.t.write_ledger(retired={"ROLD": {"claims": [], "successor": None}})
rc, out = self.t.retire(["ROLD=AX01"])
self.assertEqual(rc, 1)
self.assertIn("already retired", out)
def test_rejects_hinted_old(self):
self.t.write_ledger(hints={"HOLD": "AX01"})
rc, out = self.t.retire(["HOLD=AX01"])
self.assertEqual(rc, 1)
self.assertIn("hint key", out)
def test_rejects_dead_successor(self):
rc, out = self.t.retire(["OFOLD001=OFnope99"])
self.assertEqual(rc, 1)
self.assertIn("not a live tree id", out)
def test_rejects_malformed_pair(self):
rc, out = self.t.retire(["OFOLD001"])
self.assertEqual(rc, 1)
self.assertIn('expects "OLD=NEW"', out)
# ---------------------------------------------------------------------------
# default run: mint + insert
# ---------------------------------------------------------------------------
@@ -1261,6 +1326,21 @@ class TestRemint(SyntheticTreeCase):
changed, errors, _out = self.t.remint(["VendorA"])
self.assertEqual((changed, errors), (0, 0))
def test_salted_declaration_survives_remint(self):
# A deliberate salt split (a second preset of one product kept on its
# own id for per-printer AMS disambiguation) is mint-conformant and
# must not be converged back onto salt 0.
salt1 = afi.generate_filament_id(*self.TRIPLE, salt=1)
self.t.write_preset("VendorA", preset("APLA @legacy",
filament_id=salt1,
inherits="APLA @base",
compatible_printers=["P2 0.4 nozzle"]))
changed, errors, out = self.t.remint(["VendorA"])
self.assertEqual(errors, 0, out)
self.assertEqual(changed, 1) # only the non-conformant AX01 root
legacy = load_json_file(self.t.preset_path("VendorA", "APLA @legacy"))
self.assertEqual(legacy["filament_id"], salt1)
def test_same_triple_converges_within_run(self):
self.t.add_vendor("VendorB", [
preset("APLA @Bbase", filament_id="BX01", instantiation=False,