mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
fix(queue): enforce sliced-model compatibility on cross-model dispatch (#2578)
A queue item's "Any <model>" button labeled itself from the file's slice metadata while the scheduler used the row's target_model, so an X1C-sliced item targeting H2D showed "Any X1C" above "assign to first idle H2D". The mismatch itself was created silently: sliced-for metadata loads async, and switching to model mode before it arrived pre-selected the alphabetically first model (H2D on a mixed farm), after which the model dropdown hid itself. Nothing validated compatibility, so the scheduler would hand X1C G-code to an H2D. Frontend: never default the target silently, keep the dropdown visible in model mode (incompatible models disabled), label from the actual target, warn on mismatch, block submit when incompatible. Backend: new GCODE_COMPAT_FAMILIES table (X1/X1C/X1E/P1P/P1S interchange; everything else exact-match; missing metadata never blocks). Queue create and update reject incompatible targets with 400; the scheduler holds back pre-existing mismatched rows with an actionable waiting_reason instead of dispatching them.
This commit is contained in:
@@ -281,6 +281,41 @@ def get_rod_type(model: str | None) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
# G-code interchange families (#2578). A sliced 3MF may target a different
|
||||
# model ONLY within its family: same kinematics, build volume and G-code
|
||||
# dialect. X1/P1 series are the one proven-interchangeable group (256mm
|
||||
# CoreXY, single nozzle — mixed farms intentionally run X1-sliced jobs on
|
||||
# P1S/P1P). Everything else is exact-match only; extend deliberately, never
|
||||
# by assumption — a wrong entry here dispatches G-code onto hardware it was
|
||||
# not sliced for.
|
||||
# Short display names only (uppercase, no spaces) — is_gcode_compatible()
|
||||
# resolves internal codes (C11, O1D, ...) to short names before lookup.
|
||||
GCODE_COMPAT_FAMILIES = (frozenset(["X1", "X1C", "X1E", "P1P", "P1S"]),)
|
||||
|
||||
|
||||
def is_gcode_compatible(sliced_for_model: str | None, target_model: str | None) -> bool:
|
||||
"""Return True when G-code sliced for one model may be dispatched to the other.
|
||||
|
||||
Unknown/missing metadata on either side returns True — we can only
|
||||
validate what the 3MF declares, and legacy files without
|
||||
``sliced_for_model`` must keep working.
|
||||
"""
|
||||
if not sliced_for_model or not target_model:
|
||||
return True
|
||||
|
||||
def _norm(model: str) -> str:
|
||||
# Internal codes (e.g. "C11") → short names first, so "C11" vs "X1C"
|
||||
# compares equal instead of leaning on family membership.
|
||||
resolved = PRINTER_MODEL_ID_MAP.get(model.strip(), model)
|
||||
return resolved.strip().upper().replace(" ", "").replace("-", "")
|
||||
|
||||
a = _norm(sliced_for_model)
|
||||
b = _norm(target_model)
|
||||
if a == b:
|
||||
return True
|
||||
return any(a in family and b in family for family in GCODE_COMPAT_FAMILIES)
|
||||
|
||||
|
||||
def normalize_printer_model_id(model_id: str | None) -> str | None:
|
||||
"""Convert printer_model_id (internal code) to normalized short name.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user