profiles: deterministic setting_id from vendor/type/name (#14432)
* profiles: enforce globally-unique, per-vendor-namespaced setting_id Many non-Bambu vendors copied Bambu's generic setting_ids (GFSA04 alone appeared in 1557 files), so setting_id was not globally unique. This namespaces every vendor's ids and reserves Bambu/OrcaFilamentLibrary space. - Reserve "G*" (Bambu) and "O*" (OrcaFilamentLibrary) id spaces. - Assign each other vendor a 2-char prefix (first+last letter, collision resolved) and renumber every instantiated preset to <PREFIX><NNNN>. - Strip setting_id from base profiles (instantiation:false) per Bambu's convention; assign one to instantiated presets that lacked it. - Remove the pre-existing misspelled "settings_id" key (91 files). - filament_id is left untouched (it is a per-material id). - Add one-time migration script scripts/assign_vendor_setting_ids.py with a persisted registry resources/profiles/vendor_prefixes.json. Re-runs freeze existing ids; only new vendors/profiles get new ids. - Bump version in each changed vendor index file. - Extend scripts/orca_extra_profile_check.py with a CI guard: global uniqueness, in-namespace, no base setting_id, no gaps, no settings_id typo. 7425 profile files changed across 61 vendors; 0 cross-vendor collisions; validator clean; migration idempotent. BBL and OrcaFilamentLibrary id spaces untouched. * profiles: add setting_id authoring guide for new vendors / profiles * profiles: drop in-repo README; setting_id guide now lives in the wiki * profiles: derive setting_id deterministically from vendor/type/name * bump profile version
This commit is contained in:
@@ -3,6 +3,8 @@ import json
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from assign_vendor_setting_ids import generate_preset_setting_id
|
||||
|
||||
OBSOLETE_KEYS = {
|
||||
"acceleration", "scale", "rotate", "duplicate", "duplicate_grid",
|
||||
"bed_size", "print_center", "g0", "wipe_tower_per_color_wipe",
|
||||
@@ -450,6 +452,101 @@ def check_conflict_keys(profiles_dir, vendor_name):
|
||||
return error_count, warn_count
|
||||
|
||||
|
||||
# Bambu (BBL) keeps its authoritative "G*" cloud ids, which are NOT produced by the
|
||||
# deterministic formula, so BBL is exempt from the formula match (Rule 2) only. It is
|
||||
# still checked for presence, uniqueness, base-no-id and the typo key like every other
|
||||
# vendor. Every other vendor (incl. OrcaFilamentLibrary and Custom) must also match the
|
||||
# formula.
|
||||
SETTING_ID_FORMULA_EXEMPT_VENDORS = {"BBL"}
|
||||
PROFILE_SUBDIRS = ("filament", "process", "machine")
|
||||
|
||||
|
||||
def check_setting_id_uniqueness(profiles_dir):
|
||||
"""
|
||||
Validate setting_id across every vendor (see scripts/assign_vendor_setting_ids.py):
|
||||
1. Every instantiated preset must HAVE a setting_id. (all vendors)
|
||||
2. A stored setting_id must equal generate_preset_setting_id(vendor, type, name); a stale
|
||||
value means the JSON was edited without rerunning assign_vendor_setting_ids.py.
|
||||
(all vendors EXCEPT the formula-exempt ones, e.g. BBL)
|
||||
3. Base profiles (instantiation != "true") must not carry a setting_id. (all vendors)
|
||||
4. setting_id must be globally unique - no two files may share one. (all vendors)
|
||||
5. No profile may use the misspelled key "settings_id". (all vendors)
|
||||
Formula-exempt vendors (BBL) keep their authoritative ids, so only Rule 2 is skipped
|
||||
for them; they are still held to presence, uniqueness, base-no-id and the typo check.
|
||||
"""
|
||||
errors = 0
|
||||
owners = {} # setting_id -> list of relative_path (every vendor)
|
||||
for vendor_dir in sorted(profiles_dir.iterdir()):
|
||||
if not vendor_dir.is_dir():
|
||||
continue
|
||||
vendor = vendor_dir.name
|
||||
formula_exempt = vendor in SETTING_ID_FORMULA_EXEMPT_VENDORS
|
||||
for sub in PROFILE_SUBDIRS:
|
||||
base = vendor_dir / sub
|
||||
if not base.is_dir():
|
||||
continue
|
||||
for file_path in base.rglob("*.json"):
|
||||
try:
|
||||
data = json.loads(file_path.read_bytes())
|
||||
except (ValueError, OSError):
|
||||
continue
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
rel = file_path.relative_to(profiles_dir)
|
||||
# Rule 5: catch the misspelled "settings_id" key.
|
||||
if "settings_id" in data:
|
||||
errors += 1
|
||||
print_error(
|
||||
f'profile {rel} uses the misspelled key "settings_id" '
|
||||
f'(should be "setting_id"); run assign_vendor_setting_ids.py'
|
||||
)
|
||||
sid = data.get("setting_id")
|
||||
instantiated = data.get("instantiation") == "true"
|
||||
if not instantiated:
|
||||
# Rule 3: base/template profiles must not carry a setting_id.
|
||||
if sid:
|
||||
errors += 1
|
||||
print_error(
|
||||
f'base profile {rel} (instantiation != "true") must not have a '
|
||||
f'setting_id ("{sid}"); run assign_vendor_setting_ids.py'
|
||||
)
|
||||
continue
|
||||
# Rule 1: every instantiated preset must have a setting_id.
|
||||
if not sid:
|
||||
errors += 1
|
||||
print_error(
|
||||
f"instantiated preset {rel} is missing a setting_id; "
|
||||
f"run assign_vendor_setting_ids.py"
|
||||
)
|
||||
continue
|
||||
# Rule 2: the stored id must match the deterministic rule. BBL keeps its
|
||||
# authoritative G* ids and is exempt from this check only.
|
||||
if not formula_exempt:
|
||||
expected = generate_preset_setting_id(vendor, sub, data.get("name", ""))
|
||||
if sid != expected:
|
||||
errors += 1
|
||||
print_error(
|
||||
f'setting_id "{sid}" in {rel} does not match the expected '
|
||||
f'"{expected}" for {vendor}/{sub}/{data.get("name", "")}; '
|
||||
f"run assign_vendor_setting_ids.py"
|
||||
)
|
||||
continue
|
||||
# Rule 4: collect for the global-uniqueness check below.
|
||||
owners.setdefault(sid, []).append(rel)
|
||||
|
||||
# Rule 4: a setting_id shared by two files is an error. For managed vendors this means
|
||||
# a duplicate vendor/type/name; for formula-exempt vendors (BBL) a copy-pasted id.
|
||||
for sid, locs in sorted(owners.items()):
|
||||
if len(locs) < 2:
|
||||
continue
|
||||
errors += 1
|
||||
print_error(
|
||||
f'setting_id "{sid}" is shared by {len(locs)} files ({sorted(map(str, locs))}); '
|
||||
f"setting_id must be globally unique"
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check 3D printer profiles for common issues",
|
||||
@@ -505,6 +602,10 @@ def main():
|
||||
continue
|
||||
run_checks(vendor_dir.name)
|
||||
|
||||
# Global (cross-vendor) check: setting_id must be unique and stay in-namespace.
|
||||
# Runs once over the whole tree regardless of the --vendor filter.
|
||||
errors_found += check_setting_id_uniqueness(profiles_dir)
|
||||
|
||||
# ✨ Output finale in stile "compilatore"
|
||||
print("\n==================== SUMMARY ====================")
|
||||
print_info(f"Checked vendors : {checked_vendor_count}")
|
||||
|
||||
Reference in New Issue
Block a user