fix(printers): show correct plate thumbnail on multi-plate 3MFs (#1166)

P1S 01.10.00.00 (and similar firmware revisions) only echo the .3mf
  filename in print.gcode_file, dropping the Metadata/plate_N.gcode path.
  The /cover route's regex falls back to plate 1 — and the printer card
  shows the wrong plate's thumbnail on multi-plate prints.

  Resolution order in the new resolve_plate_id() helper (used by both
  the status route's current_plate_id and /cover):

  1. The plate Bambuddy dispatched. start_print() now records
     (dispatched_plate_id, dispatched_subtask) on PrinterState; the
     subtask check rejects stale records from a previous Bambuddy
     dispatch bleeding into a Studio-direct print on the same project.
  2. plate_(\d+)\.gcode regex on state.gcode_file (existing behaviour
     for firmware that does include the path).
  3. After download, scan the 3MF for a unique Metadata/plate_*.gcode —
     covers per-plate archives sliced separately in Studio without a
     Bambuddy dispatch record.
  4. Default to plate 1.

  Cover-byte cache key simplified to (subtask_name, view_key) now that
  plate resolution is late-bound. clear_cover_cache() already fires on
  every print start, so re-dispatches with a different plate always
  fetch a fresh thumbnail.

  Bambuddy-dispatched prints additionally register the local archive
  3MF in the cover cache at dispatch time, so /cover reads straight
  from the archive directory and doesn't refetch the file over FTP
  from a printer whose FTP server is busy serving the active print.

  Coverage: 5 unit tests for resolve_plate_id, 4 unit tests for the
  dispatch record on start_print, 2 integration tests for the cover
  route (dispatch wins over plate-1 default; 3MF-scan fallback for
  per-plate archive without dispatch record).
This commit is contained in:
maziggy
2026-04-29 16:51:38 +02:00
parent b45ca2a662
commit 889c8bd87f
9 changed files with 346 additions and 18 deletions
@@ -25,6 +25,7 @@ from backend.app.models.library import LibraryFile
from backend.app.models.printer import Printer
from backend.app.services.archive import ArchiveService
from backend.app.services.bambu_ftp import (
cache_3mf_download,
delete_file_async,
get_ftp_retry_settings,
upload_file_async,
@@ -684,6 +685,12 @@ class BackgroundDispatchService:
)
raise RuntimeError("Failed to start print")
# Register the archive's local 3MF in the cover-cache so the
# /cover endpoint can skip FTP — we already have the file on
# disk, no need to refetch 36 MB from a printer whose FTP is
# busy serving the active print (#1166 follow-up).
cache_3mf_download(job.printer_id, remote_filename, file_path)
# Wait for the printer to actually pick up the command before
# marking the dispatch job complete (#1042). MQTT-publish success
# only proves the command queued locally; the printer can still
@@ -884,6 +891,10 @@ class BackgroundDispatchService:
await db.rollback()
raise RuntimeError("Failed to start print")
# Same as the archive path: register the library file's local
# 3MF in the cover-cache so /cover skips FTP (#1166 follow-up).
cache_3mf_download(job.printer_id, remote_filename, file_path)
# See _run_reprint_archive for rationale (#1042). On timeout
# also rolls back the freshly-created archive so the library
# flow doesn't leave behind a phantom row for a print that