Browse Source

Fix virtual printer proxy mode always defaulting to X1C model

  When creating a virtual printer in proxy mode, the model was always set
  to X1C because the frontend hides the model dropdown and the backend
  used a hardcoded default. Now auto-inherits the model from the target
  printer in proxy mode — on create, on target printer change, and on
  mode switch to proxy.
maziggy 2 months ago
parent
commit
685d04df67
2 changed files with 21 additions and 3 deletions
  1. 1 0
      CHANGELOG.md
  2. 20 3
      backend/app/api/routes/virtual_printers.py

+ 1 - 0
CHANGELOG.md

@@ -25,6 +25,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Prometheus Build Info Metric** ([#633](https://github.com/maziggy/bambuddy/pull/633)) — Added a `bambuddy_build_info` gauge metric to the Prometheus metrics endpoint, exposing the application version, Python version, platform, and architecture as labels. Follows the standard Prometheus `_build_info` convention for dashboards and version-change alerting. Contributed by @sw1nn.
 
 ### Fixed
+- **Virtual Printer Proxy Mode Always Shows X1C Model** — Creating a virtual printer in Proxy mode always set the model to X1C regardless of the destination printer, because the frontend hides the model dropdown in proxy mode and the backend defaulted to X1C. Now auto-inherits the model from the target printer when creating or updating a proxy virtual printer (e.g. a proxy pointing at a P1S correctly presents itself as P1S to the slicer). The model also auto-updates when changing the target printer or switching to proxy mode.
 - **Cloud Profiles Shared Across All Users** ([#665](https://github.com/maziggy/bambuddy/issues/665)) — When authentication was enabled, Bambu Cloud credentials were stored globally — one account per Bambuddy instance. If User A logged into Cloud, every other user saw User A's account and profiles. User B logging in would overwrite User A's credentials. Cloud credentials are now stored per-user: each user logs into their own Bambu Cloud account independently. When auth is disabled (single-user mode), behavior is unchanged. Also fixed cloud data endpoints (`/cloud/settings`, `/cloud/fields`, preset CRUD) requiring `settings:read` / `settings:update` permissions instead of `cloud:auth` — users who had "Cloud Auth" enabled but "Settings" disabled couldn't load profiles after logging in. Reported by @cadtoolbox.
 - **Local Profiles Not Shown in AMS Slot Configuration** — Imported local filament profiles were hidden in the AMS slot configure modal when a printer model was set. The `compatible_printers` filter parsed the stored JSON array as a semicolon-delimited string, so the matching always failed and every local preset was silently skipped. Removed the filter entirely — user-imported profiles should be available on any printer.
 - **Interface Aliases Not Shown in Virtual Printer Interface Select** — Interface aliases (e.g. `eth0:1`) added for multi-virtual-printer setups were invisible in the bind IP dropdown. The Docker image didn't include `iproute2`, so the `ip` command wasn't available and the code fell back to ioctl-based enumeration which can only return one IP per interface. Added `iproute2` to the Docker image.

+ 20 - 3
backend/app/api/routes/virtual_printers.py

@@ -128,11 +128,13 @@ async def create_virtual_printer(
                 return JSONResponse(status_code=400, content={"detail": "Access code is required when enabling"})
 
     # Validate proxy target printer exists
+    target_printer = None
     if body.target_printer_id:
         from backend.app.models.printer import Printer
 
         result = await db.execute(select(Printer).where(Printer.id == body.target_printer_id))
-        if not result.scalar_one_or_none():
+        target_printer = result.scalar_one_or_none()
+        if not target_printer:
             return JSONResponse(
                 status_code=400, content={"detail": f"Printer with ID {body.target_printer_id} not found"}
             )
@@ -169,7 +171,9 @@ async def create_virtual_printer(
         name=body.name,
         enabled=body.enabled,
         mode=body.mode,
-        model=body.model or DEFAULT_VIRTUAL_PRINTER_MODEL,
+        model=body.model
+        or (target_printer.model if target_printer and target_printer.model and body.mode == "proxy" else None)
+        or DEFAULT_VIRTUAL_PRINTER_MODEL,
         access_code=body.access_code,
         target_printer_id=body.target_printer_id,
         auto_dispatch=body.auto_dispatch,
@@ -264,11 +268,15 @@ async def update_virtual_printer(
         from backend.app.models.printer import Printer
 
         result = await db.execute(select(Printer).where(Printer.id == body.target_printer_id))
-        if not result.scalar_one_or_none():
+        target_printer = result.scalar_one_or_none()
+        if not target_printer:
             return JSONResponse(
                 status_code=400, content={"detail": f"Printer with ID {body.target_printer_id} not found"}
             )
         vp.target_printer_id = body.target_printer_id
+        # Auto-inherit model from target printer in proxy mode (unless user explicitly set model)
+        if body.model is None and vp.mode == "proxy" and target_printer.model:
+            vp.model = target_printer.model
     if body.auto_dispatch is not None:
         vp.auto_dispatch = body.auto_dispatch
     if body.bind_ip is not None:
@@ -276,6 +284,15 @@ async def update_virtual_printer(
     if body.remote_interface_ip is not None:
         vp.remote_interface_ip = body.remote_interface_ip
 
+    # Auto-inherit model when switching to proxy mode with existing target printer
+    if body.mode == "proxy" and body.model is None and body.target_printer_id is None and vp.target_printer_id:
+        from backend.app.models.printer import Printer as PrinterModel
+
+        result = await db.execute(select(PrinterModel).where(PrinterModel.id == vp.target_printer_id))
+        existing_target = result.scalar_one_or_none()
+        if existing_target and existing_target.model:
+            vp.model = existing_target.model
+
     # Determine final enabled state
     explicitly_enabling = body.enabled is True
     new_enabled = body.enabled if body.enabled is not None else vp.enabled