diff --git a/NIGHTLY_CHANGELOG.md b/NIGHTLY_CHANGELOG.md
index 492b142..9055ef9 100644
--- a/NIGHTLY_CHANGELOG.md
+++ b/NIGHTLY_CHANGELOG.md
@@ -1,4 +1,7 @@
## Changes in this build
-- Fix: wrong filament slot used in multicolor prints when T0 is unused — AMS box mapping now includes placeholder entries for all paint indices so the printer correctly matches T1/T2/T3 tool changes to the right slots (Issue #78)
-- Fix: Spoolman spool-slot assignment now persisted to config.ini and restored on bridge restart
+- Fix: Spoolman spool-slot assignment persistence was silently broken (NameError on config_loader swallowed by bare except) — now uses correct module reference and logs failures; per-printer isolation added so multi-printer setups no longer overwrite each other (Issue #80, PR #83 by @walterioo)
+- Fix: spool dropdown in print dialog showed "[object Object]" instead of vendor name (PR #83)
+- Fix: gate_spool_id in MMU object now populated from per-printer slot map instead of hardcoded -1 (PR #83)
+- Fix: PLA variants (PLA+, PLA Silk, PLA Matte) now recognized as their material family — correct tray_info_idx sent to printer, correct Generic profile synced to OrcaSlicer, and vendor profiles for the variant shown in slot editor dropdown (Issue #82)
+- UI: PLA+, PLA Silk and PLA Matte buttons added to slot material selector
diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py
index 2deb518..c08fe00 100644
--- a/kobrax_moonraker_bridge.py
+++ b/kobrax_moonraker_bridge.py
@@ -1855,23 +1855,55 @@ class KobraXBridge:
# `compatible_printers: []` (= mit allen Druckern kompatibel).
_TRAY_INFO_IDX = {
# Anycubic-eigene Kobra-X-Profile
- "PLA": "GFPLA",
- "PLA+": "GFPLA+",
- "PLA SILK": "GFPLA Silk",
- "PETG": "GFPETG",
- "ABS": "GFABS",
- "ASA": "GFASA",
- "TPU": "GFTPU 95A",
- "PVA": "GFPVA",
+ "PLA": "GFPLA",
+ "PLA+": "GFPLA+",
+ "PLA SILK": "GFPLA Silk",
+ "PLA-SILK": "GFPLA Silk",
+ "PLASILK": "GFPLA Silk",
+ "SILK PLA": "GFPLA Silk",
+ "PLA MATTE": "GFPLA",
+ "PLA-MATTE": "GFPLA",
+ "PLA MARBLE": "GFPLA",
+ "PLA WOOD": "GFPLA",
+ "PETG": "GFPETG",
+ "PETG+": "GFPETG",
+ "ABS": "GFABS",
+ "ASA": "GFASA",
+ "TPU": "GFTPU 95A",
+ "TPE": "GFTPU 95A",
+ "PVA": "GFPVA",
# Kein Anycubic-Kobra-X-Profil → Library-Fallback
- "PLA-CF": "OGFL98",
- "PETG-CF": "OGFG98",
- "PA": "OGFN99",
- "PA-CF": "OGFN98",
- "PC": "OGFC99",
- "HIPS": "OGFS98",
+ "PLA-CF": "OGFL98",
+ "PLA CF": "OGFL98",
+ "PETG-CF": "OGFG98",
+ "PETG CF": "OGFG98",
+ "PA": "OGFN99",
+ "PA-CF": "OGFN98",
+ "PA CF": "OGFN98",
+ "PC": "OGFC99",
+ "HIPS": "OGFS98",
}
+ # Normalisiert Material-Typ-Strings auf den kanonischen Key für _TRAY_INFO_IDX
+ # und _default_filament_name. PLA-Varianten die nicht exakt matchen fallen
+ # auf ihre Basisfamilie zurück (PLA+ → PLA+, PLA Matte → PLA, etc.).
+ @staticmethod
+ def _normalize_material(mat: str) -> str:
+ m = mat.upper().strip().replace("-", " ").replace("_", " ")
+ # Bekannte Varianten normalisieren
+ _ALIASES = {
+ "PLAPLUS": "PLA+", "PLA PLUS": "PLA+",
+ "SILK PLA": "PLA SILK", "PLASILK": "PLA SILK",
+ "PLA MATTE": "PLA MATTE", "PLA MARBLE": "PLA MARBLE",
+ "PLA WOOD": "PLA WOOD",
+ "TPE": "TPU",
+ "PETG PLUS": "PETG+",
+ "PA6": "PA", "PA12": "PA", "PA66": "PA",
+ }
+ if m in _ALIASES:
+ return _ALIASES[m]
+ return m
+
def _build_lane_data(self) -> dict:
"""Baut BBL-AMS-JSON für OrcaSlicer DevFilaSystemParser::ParseV1_0.
@@ -1910,7 +1942,7 @@ class KobraXBridge:
color_hex = color_raw[:6].upper() + "FF"
else:
color_hex = "FFFFFFFF"
- material = slot.get("type", "PLA").upper()
+ material = self._normalize_material(slot.get("type", "PLA"))
# User-Override aus config.ini [filament_profiles].slot_N_id
# bekommt Vorrang vor dem Default-Mapping nach material-Type.
# Vendor wird mitgesendet (tray_sub_brands + filament_vendor),
@@ -2086,7 +2118,7 @@ class KobraXBridge:
for _global_index, slot in slots:
occupied = slot.get("status") == 5
gate_status.append(1 if occupied else 0)
- material = (slot.get("type") or "PLA").upper() if occupied else ""
+ material = self._normalize_material(slot.get("type") or "PLA") if occupied else ""
gate_material.append(material)
c = slot.get("color", [0, 0, 0]) if occupied else [0, 0, 0]
# Happy Hare erwartet gate_color als RRGGBB OHNE '#' (Klipper-Limitation).
@@ -2136,8 +2168,22 @@ class KobraXBridge:
kann pro Slot eine konkrete Marke setzen wenn er das will."""
if not material:
return ""
- mat = material.upper().strip()
+ mat = self._normalize_material(material)
profs = self._load_orca_filaments()
+ # Varianten-Mapping: Drucker meldet z.B. "PLA SILK", OrcaSlicer speichert
+ # alle Varianten unter type=PLA mit dem Variant-Namen im name-Feld.
+ _VARIANT_NAME = {
+ "PLA SILK": "Generic PLA Silk",
+ "PLA MATTE": "Generic PLA Matte",
+ "PLA+": "Generic PLA",
+ "PLA-CF": "Generic PLA-CF",
+ "PETG-CF": "Generic PETG-CF",
+ }
+ if mat in _VARIANT_NAME:
+ target = _VARIANT_NAME[mat]
+ for p in profs:
+ if p.get("vendor") == "Generic" and p.get("name") == target:
+ return p["name"]
def _match_type(p: dict) -> bool:
pt = (p.get("type") or "").upper()
return pt == mat or pt.startswith(mat + "-") or pt.startswith(mat + " ")
diff --git a/web/themes/default/app.js b/web/themes/default/app.js
index 6f80ba4..696c1bc 100644
--- a/web/themes/default/app.js
+++ b/web/themes/default/app.js
@@ -1413,7 +1413,14 @@ function doProfileImportUpload(files){
// ── AMS Slot Edit ──
var _slotEditIndex=-1;
var _slotEditLoaded=false;
-var _MAT_PRESETS=['PLA','PETG','ABS','ASA','TPU','PA','PC','HIPS'];
+var _MAT_PRESETS=['PLA','PLA+','PLA SILK','PLA MATTE','PETG','ABS','ASA','TPU','PA','PC','HIPS'];
+function _normalizeMat(m){
+ var s=m.toUpperCase().trim().replace(/-/g,' ').replace(/_/g,' ');
+ var aliases={'PLAPLUS':'PLA+','PLA PLUS':'PLA+','SILK PLA':'PLA SILK','PLASILK':'PLA SILK',
+ 'PLA MATTE':'PLA','PLA MARBLE':'PLA','PLA WOOD':'PLA','TPE':'TPU',
+ 'PETG PLUS':'PETG+','PA6':'PA','PA12':'PA','PA66':'PA'};
+ return aliases[s]||s;
+}
var _BASE_MATERIAL_TYPES=['PLA','PETG','ABS','ASA','TPU','TPE','PA','PC','HIPS','PEI','PEEK'];
function updateSlotEditFeedButton(){
var btn=document.getElementById('btn-slot-edit-feed');
@@ -1454,10 +1461,21 @@ function _fillSlotProfileDropdown(material, currentVendor, currentName){
_loadOrcaFilaments(function(profiles){
// Type-Filter: nur Profile vom passenden material zeigen (z.B. PLA → alle PLA-Varianten)
var matU=(material||'').toUpperCase().trim();
+ // PLA-Varianten: Drucker meldet "PLA SILK"/"PLA+"/"PLA MATTE", OrcaSlicer
+ // speichert alle unter type=PLA — Namens-Keyword als Zusatzfilter.
+ var _PLA_VARIANT_KW={'PLA SILK':'silk','PLA+':'pla+','PLA MATTE':'matte',
+ 'PLA MARBLE':'marble','PLA WOOD':'wood'};
+ var variantKw=_PLA_VARIANT_KW[matU]||null;
+ var baseMat=variantKw?'PLA':matU;
var matched=profiles.filter(function(p){
var pt=(p.type||'').toUpperCase();
- // PLA-CF, PLA-SILK etc. zählen auch zu PLA
- return matU==='' || pt===matU || pt.startsWith(matU+'-') || pt.startsWith(matU+' ');
+ var nameL=(p.name||'').toLowerCase();
+ // Basis-Typ muss passen
+ var typeOk=baseMat===''||pt===baseMat||pt.startsWith(baseMat+'-')||pt.startsWith(baseMat+' ');
+ if(!typeOk) return false;
+ // Bei Variante: Name muss Keyword enthalten (z.B. "silk", "matte", "pla+")
+ if(variantKw) return nameL.indexOf(variantKw)!==-1;
+ return true;
});
sel.innerHTML='';
// User-Profile (is_user) zuerst — eigene Optgroup '★ Eigene' an erster Stelle.
@@ -1619,11 +1637,14 @@ function openSlotEdit(i){
_renderCopyFromSlot(globalIdx);
var mat=(slot.type||'PLA').toUpperCase();
document.getElementById('slot-edit-mat').value=mat;
+ // Normalisieren für Button-Highlighting: PLA-Varianten auf nächsten Preset mappen
+ var matNorm=_normalizeMat(mat);
var btns=document.getElementById('slot-mat-btns');
btns.innerHTML=_MAT_PRESETS.map(function(m){
+ var active=m===mat||m===matNorm;
return '';
+ +(active?'background:var(--accent);color:#fff':'background:var(--raised);color:var(--txt2)')+'">'+m+'';
}).join('');
// OrcaSlicer-Profil-Dropdown: aktuellen User-Override für diesen Slot
// aus /kx/filament/slots holen (enthält vendor+name+id).