fix(filament): suppress stale slot profile when AMS material family changes
Some checks failed
PR Check / lint-and-test (pull_request) Has been cancelled
Nightly Build / build (push) Successful in 14m49s

A per-slot filament profile override (config.ini [filament_profiles]) stores
only {vendor, name, id} and is sticky: swapping the physical filament updates
the AMS colour + type live, but the saved profile persisted. Loading yellow PLA
into a slot that held "KINGROON PETG Basic" kept showing/sending PETG in the
panel and the OrcaSlicer lane hint, and survived restarts (config.ini).

Resolve the effective profile per slot as the saved override only when its
material *family* still matches the loaded AMS material — PLA / PLA+ / PLA SILK /
PLA MATTE are one family, so within-family swaps never invalidate a valid
profile (guards against the earlier over-strict material compare). On a family
change the override is suppressed (slot falls back to the generic default) but
NOT deleted, so re-loading the original material reactivates it. Profile
material is resolved from the Orca filament library; unknown → never suppress.

Wired into the three resolution sites: handle_kx_filament_slots (panel),
_build_lane_data (OrcaSlicer AMS array), _build_mmu_object (gate_filament_name).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Walter Almada B
2026-07-08 23:06:31 -07:00
parent eda14db897
commit bf94a8f563
3 changed files with 227 additions and 3 deletions

View File

@ -1917,6 +1917,55 @@ class KobraXBridge:
return _ALIASES[m]
return m
@staticmethod
def _material_family(mat: str) -> str:
"""Reduce a material to its base polymer family.
PLA / PLA+ / PLA SILK / PLA MATTE -> "PLA"; PETG / PETG+ -> "PETG"; etc.
Used by the stale-profile guard: only a change of *family* (e.g. PETG ->
PLA) invalidates a saved slot profile — a change within the family
(PLA -> PLA SILK) must not discard an otherwise valid profile.
"""
if not mat:
return ""
m = KobraXBridge._normalize_material(mat)
# Longer prefixes first so "PETG" is not swallowed by "PET".
for fam in ("PETG", "PLA", "ABS", "ASA", "TPU", "PVA", "HIPS", "PA", "PC", "PET"):
if m.startswith(fam):
return fam
return m
def _profile_material(self, profile: dict) -> str:
"""Material type (e.g. "PETG") of a saved slot profile, resolved by
(vendor, name) from the Orca filament library. Returns "" when the
profile is not in the library — we do NOT guess in that case."""
name = (profile or {}).get("name", "")
if not name:
return ""
vendor = profile.get("vendor", "")
for p in self._load_orca_filaments():
if p.get("vendor") == vendor and p.get("name") == name:
return p.get("type", "") or ""
return ""
def _effective_slot_profile(self, global_idx: int, ams_material: str) -> dict:
"""Saved slot-profile override — but only while its material *family*
still matches the material currently loaded in the AMS.
Non-destructive suppression (Option A): when the family no longer matches
(e.g. a PETG profile but PLA loaded) we return {} → the slot falls back to
the generic default. The override stays in config.ini and reactivates as
soon as the matching material is loaded again. When the profile's family
is unknown we do NOT suppress (fail-safe)."""
profile = self._filament_profiles.get(global_idx) or {}
if not profile.get("name"):
return {}
prof_fam = self._material_family(self._profile_material(profile))
ams_fam = self._material_family(ams_material)
if prof_fam and ams_fam and prof_fam != ams_fam:
return {}
return profile
def _build_lane_data(self) -> dict:
"""Builds BBL AMS JSON for OrcaSlicer DevFilaSystemParser::ParseV1_0.
@ -1965,7 +2014,10 @@ class KobraXBridge:
# 1. User-Wahl (config.ini [filament_profiles]) — exakte Kontrolle
# 2. Generic fallback (_TRAY_INFO_IDX) per material type - no
# vendor hint; OrcaSlicer then picks its own generic preset
user_profile = self._filament_profiles.get(slot_index) or {}
# Stale-profile guard: only apply the override while its material
# family still matches the loaded filament (PETG profile + PLA
# loaded -> dropped).
user_profile = self._effective_slot_profile(slot_index, material)
if user_profile.get("name"):
vendor = user_profile.get("vendor", "")
fila_name = user_profile.get("name", "")
@ -2145,7 +2197,9 @@ class KobraXBridge:
# preset name -> 'Anycubic PLA' matches the printer-specific
# preset; an empty string previously led to Generic PLA.
if occupied:
user_profile = self._filament_profiles.get(_global_index) or {}
# Stale-profile guard (see _effective_slot_profile): only apply the
# override while its material family still matches the loaded filament.
user_profile = self._effective_slot_profile(_global_index, material)
fila_name = user_profile.get("name") or self._default_filament_name(material)
gate_filament_name.append(fila_name)
else:
@ -2484,7 +2538,9 @@ class KobraXBridge:
slots = []
for i, s in enumerate(self._ams_slots):
gidx = int(s.get("global_index", i))
profile = self._filament_profiles.get(gidx) or {}
# Stale-profile guard: only show the override while its material
# family matches the loaded AMS material (else slot has no brand).
profile = self._effective_slot_profile(gidx, s.get("type", ""))
slots.append({
"slot_index": gidx,
"material": s.get("type", ""),