فهرست منبع

Fix VP queue mode sending wrong plate_id → HMS 0500_4003 (#529)

When a virtual printer auto-queued a file, PrintQueueItem was created
without plate_id. The scheduler defaulted to plate_id=1, generating
MQTT path "Metadata/plate_1.gcode". For multi-plate 3MF files sliced
on a different plate, the printer couldn't find the gcode and returned
HMS error 0500_4003. Extract plate index from the 3MF's slice_info.config
before creating the queue item.
maziggy 3 ماه پیش
والد
کامیت
a6d326f8fa
2فایلهای تغییر یافته به همراه23 افزوده شده و 0 حذف شده
  1. 1 0
      CHANGELOG.md
  2. 22 0
      backend/app/services/virtual_printer/manager.py

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.2.1] - Unreleased
 
 ### Fixed
+- **Virtual Printer Queue Sends Wrong Plate ID** ([#529](https://github.com/maziggy/bambuddy/issues/529)) — Files sent to a virtual printer in queue mode always used `plate_id=1`, generating the MQTT path `Metadata/plate_1.gcode`. For multi-plate 3MF files where the actual plate is different (e.g. plate 19), the printer couldn't find the gcode and returned HMS error 0500_4003. Now extracts the plate index from the 3MF's `slice_info.config` before creating the queue item.
 - **Unnecessary Target Model Selector on "Any" Tab** ([#528](https://github.com/maziggy/bambuddy/issues/528)) — When scheduling a print to "Any {model}", a redundant "Target Model" dropdown appeared even though the G-code is already sliced for a specific printer model. Changing the target model would lead to print failures. The dropdown is now hidden when the sliced model is known (the tab label already shows "Any {model}"). It still appears as a fallback for legacy files without model metadata.
 - **"Clear Plate & Start Next" Button Shown on Printers Without Correct Filament** ([#527](https://github.com/maziggy/bambuddy/issues/527)) — When a print job was queued for "any printer" of a model (e.g., "any H2S"), the "Clear Plate & Start Next" button appeared on ALL printers of that model, including those without the required filament loaded. Clicking it on a printer without the right filament would start a print that fails. The `PrinterQueueWidget` now filters queue items by filament compatibility — it checks the printer's loaded filament types (from AMS and external spools) against the queue item's `required_filament_types` and only shows items the printer can actually print. If no compatible items exist, the widget is hidden.
 - **Manual Spool Weight Overwritten by AMS Auto-Sync** ([#525](https://github.com/maziggy/bambuddy/issues/525)) — When a user manually entered a spool weight (via UI or API), the value was overwritten by the automatic AMS remain% sync that runs on every MQTT update. The AMS remain% is integer-only (~10g resolution for 1kg spool) and can't match precise manual entries. Added a `weight_locked` flag that is automatically set when `weight_used` is explicitly updated via the API. Locked spools are skipped by both the automatic AMS remain% sync and the manual force-sync endpoint. The usage tracker (3MF/gcode delta tracking) is unaffected. Users can re-enable AMS sync by setting `weight_locked: false`.

+ 22 - 0
backend/app/services/virtual_printer/manager.py

@@ -310,10 +310,12 @@ class VirtualPrinterInstance:
                     target_model = None
                     if not self.target_printer_id and self.model:
                         target_model = VIRTUAL_PRINTER_MODELS.get(self.model)
+                    plate_id = self._extract_plate_id(file_path)
                     queue_item = PrintQueueItem(
                         printer_id=self.target_printer_id,
                         target_model=target_model,
                         archive_id=archive.id,
+                        plate_id=plate_id,
                         position=1,
                         status="pending",
                     )
@@ -330,6 +332,26 @@ class VirtualPrinterInstance:
         except Exception as e:
             logger.error("Error adding to print queue: %s", e)
 
+    @staticmethod
+    def _extract_plate_id(file_path: Path) -> int | None:
+        """Extract plate index from 3MF slice_info.config."""
+        try:
+            import xml.etree.ElementTree as ET
+            import zipfile
+
+            with zipfile.ZipFile(file_path, "r") as zf:
+                if "Metadata/slice_info.config" in zf.namelist():
+                    content = zf.read("Metadata/slice_info.config").decode()
+                    root = ET.fromstring(content)  # noqa: S314
+                    plate = root.find(".//plate")
+                    if plate is not None:
+                        for meta in plate.findall("metadata"):
+                            if meta.get("key") == "index" and meta.get("value"):
+                                return int(meta.get("value"))
+        except Exception:
+            return None
+        return None
+
     # -- Service lifecycle --
 
     async def start_server(self) -> None: