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:
SoftFever
2026-06-27 20:11:25 +08:00
parent 8ccae50e1b
commit 6d7eeb89dc
7876 changed files with 8152 additions and 6541 deletions

View File

@@ -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}")