diff --git a/CHANGELOG.md b/CHANGELOG.md index 80e0a2151..65355a386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ All notable changes to Bambuddy will be documented in this file. - **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. - **P2S Camera Stream Disconnects After a Few Seconds** ([#661](https://github.com/maziggy/bambuddy/issues/661)) — The P2S firmware drops RTSP sessions after a few seconds with an I/O error. The backend treated this as a fatal failure, ending the MJPEG stream and forcing the frontend through a full reconnection cycle (stop → start → brief connection → fail → repeat). Added transparent auto-reconnection: when ffmpeg's RTSP connection dies, it respawns immediately and continues streaming MJPEG frames to the browser without interruption. Reported by @ddetton, confirmed by @DMoenning. - **iOS/iPadOS Cannot Reposition Floating Camera** ([#687](https://github.com/maziggy/bambuddy/issues/687)) — The floating camera viewer (embedded camera window on the dashboard) could not be dragged or resized on iOS/iPadOS because it only handled mouse events. Touch input scrolled the page underneath instead of moving the camera window. Added touch event support (`touchstart`/`touchmove`/`touchend`) to both the header drag handle and the resize handle, with `preventDefault` to stop page scrolling during drag. Reported by @dsmitty166. +- **PA-CF / PA12-CF / PAHT-CF Not Treated as Compatible** ([#688](https://github.com/maziggy/bambuddy/issues/688)) — Bambu Lab firmware treats PA-CF, PA12-CF, and PAHT-CF as interchangeable, but the print scheduler and filament override UI used exact string matching. If a 3MF required PA-CF but the AMS had PA12-CF loaded, the scheduler wouldn't assign the job and the filament override dropdown was empty/disabled. Added a filament type equivalence system so these PA variants are treated as compatible in scheduler assignment, AMS slot matching, force color match validation, and the filament override dropdown. Reported by @aneopsy. +- **Force Color Match Toggle Click Target Too Large** ([#688](https://github.com/maziggy/bambuddy/issues/688)) — In the Schedule Print modal, clicking anywhere on the "Force color match" row toggled the checkbox, not just the checkbox and its label. The click target now covers only the checkbox, icon, and label text. Reported by @aneopsy. - **Debug Logging Endpoint 500 Error** — The `GET /api/v1/support/debug-logging` endpoint returned a 500 Internal Server Error when the database contained a timezone-aware timestamp written by a previous version. The duration calculation subtracted a timezone-aware datetime from a naive `datetime.now()`, raising `TypeError`. Now strips timezone info when reading the stored timestamp. - **Bed Cooled Notification Never Fires** ([#497](https://github.com/maziggy/bambuddy/issues/497)) — The bed cooldown monitor always timed out after 30 minutes without sending a notification. After print completion, P1S (and likely other models) sends partial MQTT status updates that don't include `bed_temper`, so the cached bed temperature stayed frozen at the end-of-print value and never dropped below the threshold. The monitor now sends periodic `pushall` commands to the printer to force fresh temperature data. Also added debug logging to the polling loop for future diagnostics. - **Notification Provider Missing Event Toggles on Create** ([#497](https://github.com/maziggy/bambuddy/issues/497)) — When creating a new notification provider, the `on_bed_cooled` toggle and all 7 queue event toggles (`on_queue_job_added`, `on_queue_job_assigned`, `on_queue_job_started`, `on_queue_job_waiting`, `on_queue_job_skipped`, `on_queue_job_failed`, `on_queue_completed`) were silently discarded. The create endpoint manually listed each field but omitted these 8 toggles, so they always defaulted to `false` regardless of user selection. Editing an existing provider worked correctly. diff --git a/backend/app/services/print_scheduler.py b/backend/app/services/print_scheduler.py index 3523f50f8..0f99217cd 100644 --- a/backend/app/services/print_scheduler.py +++ b/backend/app/services/print_scheduler.py @@ -27,6 +27,23 @@ from backend.app.utils.threemf_tools import extract_nozzle_mapping_from_3mf logger = logging.getLogger(__name__) +# Filament type equivalence groups — types within the same group are +# interchangeable on the printer side (Bambu Lab firmware treats them as compatible). +_FILAMENT_TYPE_GROUPS: list[list[str]] = [ + ["PA-CF", "PA12-CF", "PAHT-CF"], +] +_FILAMENT_EQUIV_MAP: dict[str, str] = {} +for _group in _FILAMENT_TYPE_GROUPS: + _canonical = _group[0].upper() + for _t in _group: + _FILAMENT_EQUIV_MAP[_t.upper()] = _canonical + + +def _canonical_filament_type(ftype: str) -> str: + """Return canonical type for equivalence matching.""" + upper = ftype.upper() + return _FILAMENT_EQUIV_MAP.get(upper, upper) + class PrintScheduler: """Background scheduler that processes the print queue.""" @@ -484,16 +501,16 @@ class PrintScheduler: tray_color = tray.get("tray_color", "") if tray_type: color_norm = tray_color.replace("#", "").lower()[:6] - loaded.add((tray_type.upper(), color_norm)) + loaded.add((_canonical_filament_type(tray_type), color_norm)) for vt in status.raw_data.get("vt_tray") or []: vt_type = vt.get("tray_type") if vt_type: color_norm = (vt.get("tray_color", "") or "").replace("#", "").lower()[:6] - loaded.add((vt_type.upper(), color_norm)) + loaded.add((_canonical_filament_type(vt_type), color_norm)) missing = [] for o in force_overrides: - o_type = (o.get("type") or "").upper() + o_type = _canonical_filament_type(o.get("type") or "") o_color = (o.get("color") or "").replace("#", "").lower()[:6] if (o_type, o_color) not in loaded: color_label = o.get("color_name") or o.get("color", "?") @@ -515,6 +532,7 @@ class PrintScheduler: return required_types # Can't determine, assume all missing # Collect all filament types loaded on this printer (AMS units + external spool) + # Use canonical types so equivalence groups (e.g. PA-CF/PA12-CF/PAHT-CF) match. loaded_types: set[str] = set() # Check AMS units (stored in raw_data["ams"]) @@ -524,18 +542,18 @@ class PrintScheduler: for tray in ams_unit.get("tray", []): tray_type = tray.get("tray_type") if tray_type: - loaded_types.add(tray_type.upper()) + loaded_types.add(_canonical_filament_type(tray_type)) # Check external spool(s) (virtual tray, stored in raw_data["vt_tray"] as list) for vt in status.raw_data.get("vt_tray") or []: vt_type = vt.get("tray_type") if vt_type: - loaded_types.add(vt_type.upper()) + loaded_types.add(_canonical_filament_type(vt_type)) - # Find which required types are missing (case-insensitive comparison) + # Find which required types are missing (using canonical type for equivalence) missing = [] for req_type in required_types: - if req_type.upper() not in loaded_types: + if _canonical_filament_type(req_type) not in loaded_types: missing.append(req_type) return missing @@ -915,7 +933,7 @@ class PrintScheduler: if not idx_match and not exact_match and not similar_match and not type_only_match: for f in available: f_type = (f.get("type") or "").upper() - if f_type != req_type: + if _canonical_filament_type(f_type) != _canonical_filament_type(req_type): continue # Type matches - check color diff --git a/frontend/src/components/PrintModal/FilamentOverride.tsx b/frontend/src/components/PrintModal/FilamentOverride.tsx index 4f344d441..706311981 100644 --- a/frontend/src/components/PrintModal/FilamentOverride.tsx +++ b/frontend/src/components/PrintModal/FilamentOverride.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Circle, RotateCcw, Palette } from 'lucide-react'; import { getColorName } from '../../utils/colors'; +import { canonicalFilamentType } from '../../utils/amsHelpers'; import type { FilamentReqsData } from './types'; interface FilamentOverrideProps { @@ -31,11 +32,12 @@ export function FilamentOverride({ }: FilamentOverrideProps) { const { t } = useTranslation(); - // Index available filaments by type (uppercased) for per-slot filtering + // Index available filaments by canonical type for per-slot filtering. + // Types in the same equivalence group (e.g. PA-CF / PA12-CF / PAHT-CF) share one bucket. const filamentsByType = useMemo(() => { const map: Record> = {}; for (const f of availableFilaments) { - const key = f.type.toUpperCase(); + const key = canonicalFilamentType(f.type); if (!map[key]) map[key] = []; map[key].push(f); } @@ -71,7 +73,7 @@ export function FilamentOverride({ const override = overrides[req.slot_id]; const isOverridden = !!override; // Only show filaments of the same type AND compatible nozzle/extruder - const sameType = filamentsByType[req.type.toUpperCase()] || []; + const sameType = filamentsByType[canonicalFilamentType(req.type)] || []; // On dual-nozzle printers (H2D), filter to filaments on the correct extruder. // nozzle_id from 3MF maps to extruder_id from AMS. If nozzle_id is undefined // (single-nozzle) or extruder_id is null, no nozzle filtering is needed. @@ -134,7 +136,7 @@ export function FilamentOverride({ )} {/* Force Color Match checkbox — shown below each filament row */} -