diff --git a/CHANGELOG.md b/CHANGELOG.md index fa08e1352..e6ae6e6c6 100644 --- a/CHANGELOG.md +++ b/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`. diff --git a/backend/app/services/virtual_printer/manager.py b/backend/app/services/virtual_printer/manager.py index c439bd8da..35dd704d9 100644 --- a/backend/app/services/virtual_printer/manager.py +++ b/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: