mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-20 09:03:43 +02:00
Support non-0.4mm nozzles in AMS Slot config + guard dispatch (#1899)
The Configure AMS Slot picker was hardwired to 0.4mm (nozzleDiameter prop never passed from PrintersPage / SpoolBuddyAmsPage), so a 0.6 machine could only set 0.4 profiles on its trays. Resolve the real installed nozzle per-AMS (ams_extruder_map on dual-nozzle) and pass it in. Separately, nothing validated the sliced nozzle against the installed one, so a mismatch reached the printer as a cryptic HMS _8012 "Failed to get AMS mapping table". Add a fail-safe pre-dispatch guard in _start_print that fails the item with an actionable message before upload; no slice diameter or no reported nozzles = no-op.
This commit is contained in:
@@ -144,6 +144,50 @@ def _canonical_filament_type(ftype: str) -> str:
|
||||
return _FILAMENT_EQUIV_MAP.get(upper, upper)
|
||||
|
||||
|
||||
def _installed_nozzle_diameters(status) -> list[float]:
|
||||
"""Parse the installed nozzle diameters from a PrinterState (#1899).
|
||||
|
||||
Returns the diameters the printer actually reports (e.g. [0.4] single-nozzle,
|
||||
[0.4, 0.6] dual-nozzle), skipping the empty-string defaults that populate a
|
||||
NozzleInfo before MQTT fills it in. An empty list means "the printer hasn't
|
||||
told us its nozzle hardware" — callers must treat that as unknown, not as a
|
||||
mismatch, so we never block a print on missing data.
|
||||
"""
|
||||
diameters: list[float] = []
|
||||
for nozzle in getattr(status, "nozzles", None) or []:
|
||||
raw = getattr(nozzle, "nozzle_diameter", "") or ""
|
||||
try:
|
||||
value = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if value > 0:
|
||||
diameters.append(value)
|
||||
return diameters
|
||||
|
||||
|
||||
def _nozzle_mismatch_message(sliced_nozzle: float | None, installed: list[float]) -> str | None:
|
||||
"""Return an actionable error message when the sliced nozzle can't be
|
||||
printed on any installed nozzle, else None (#1899).
|
||||
|
||||
Fail-safe: returns None whenever we lack the data to judge — no sliced
|
||||
diameter, or the printer reported no nozzles — so a print is only ever
|
||||
blocked on a POSITIVE mismatch. On dual-nozzle printers a match against
|
||||
EITHER installed nozzle passes (a 0.6 slice is fine if one hotend is 0.6).
|
||||
The 0.05 tolerance absorbs float noise while staying well inside the 0.2
|
||||
gap between adjacent nozzle sizes (0.2/0.4/0.6/0.8).
|
||||
"""
|
||||
if not sliced_nozzle or not installed:
|
||||
return None
|
||||
if any(abs(d - sliced_nozzle) < 0.05 for d in installed):
|
||||
return None
|
||||
installed_str = " / ".join(f"{d:g}mm" for d in installed)
|
||||
return (
|
||||
f"File sliced for a {sliced_nozzle:g}mm nozzle, but the printer has "
|
||||
f"{installed_str} installed. Re-slice for the installed nozzle, or "
|
||||
f"install the matching nozzle before printing."
|
||||
)
|
||||
|
||||
|
||||
class PrintScheduler:
|
||||
"""Background scheduler that processes the print queue."""
|
||||
|
||||
@@ -2629,6 +2673,47 @@ class PrintScheduler:
|
||||
await self._power_off_if_needed(db, item)
|
||||
return
|
||||
|
||||
# Nozzle-diameter mismatch guard (#1899). A file sliced for one nozzle
|
||||
# size dispatched to a printer with a different nozzle installed is
|
||||
# rejected by the firmware with a cryptic HMS ("Failed to get AMS mapping
|
||||
# table" 0700_8012, or "nozzle diameter … not consistent" 0500_4038) that
|
||||
# gives the user no idea what went wrong. Catch it here, before we spend
|
||||
# time preheating and uploading, and fail with an actionable message.
|
||||
# Fail-safe by construction: only a POSITIVE mismatch blocks — when the
|
||||
# slice carries no nozzle diameter (archive.nozzle_diameter is None) or
|
||||
# the printer hasn't reported its nozzles yet, we fall through and let the
|
||||
# print proceed exactly as before. On dual-nozzle printers (H2D) a match
|
||||
# against EITHER installed nozzle passes, so a 0.6 slice is fine as long
|
||||
# as one of the two hotends is a 0.6.
|
||||
sliced_nozzle = archive.nozzle_diameter if archive else None
|
||||
if sliced_nozzle:
|
||||
installed = _installed_nozzle_diameters(printer_manager.get_status(item.printer_id))
|
||||
mismatch_msg = _nozzle_mismatch_message(sliced_nozzle, installed)
|
||||
if mismatch_msg:
|
||||
item.status = "failed"
|
||||
item.error_message = mismatch_msg
|
||||
item.completed_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
logger.warning("Queue item %s: nozzle mismatch — %s", item.id, mismatch_msg)
|
||||
await notification_service.on_queue_job_failed(
|
||||
job_name=filename.replace(".gcode.3mf", "").replace(".3mf", ""),
|
||||
printer_id=printer.id,
|
||||
printer_name=printer.name,
|
||||
reason=mismatch_msg,
|
||||
db=db,
|
||||
)
|
||||
try:
|
||||
await ws_manager.send_queue_item_failed(
|
||||
user_id=item.created_by_id,
|
||||
queue_item_id=item.id,
|
||||
printer_id=item.printer_id,
|
||||
reason="nozzle_mismatch",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
await self._power_off_if_needed(db, item)
|
||||
return
|
||||
|
||||
# Preheat / heat-soak (#1468) — fires before upload so the printer's
|
||||
# bed (and chamber, if applicable) is at temperature when the firmware
|
||||
# starts the actual print routine. Best-effort: any failure logs and
|
||||
|
||||
Reference in New Issue
Block a user