Parcourir la source

Add AMS slot info to filament_details notification variable (#336)

filament_details now shows which AMS unit and tray each filament came
from (e.g. "AMS-A T1 PLA: 12.4g | AMS-A T3 PETG: 2.8g"). External
spool holders show "Ext". Falls back to type-only format when usage
tracking data is unavailable (e.g. Spoolman mode).

- usage_tracker: include spool material in result dicts
- main.py: pass usage_results to notification flow via archive_data
- notification_service: build filament_details with AMS labels when
  usage_results available, fall back to existing slot-based format
- Update sample data and changelog
maziggy il y a 6 mois
Parent
commit
f88c7704bc

+ 1 - 1
CHANGELOG.md

@@ -8,7 +8,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Spool Inventory — AMS Slot Assignment** — Assign inventory spools to AMS slots for filament tracking. Hover over any non-Bambu-Lab AMS slot to assign or unassign spools. The assign modal filters out Bambu Lab spools (tracked via RFID) and spools already assigned to other slots. Bambu Lab spool slots automatically hide assign/unassign UI since they are managed by the AMS. When a Bambu Lab spool is inserted into a slot with a manual assignment, the assignment is automatically unlinked.
 - **Spool Inventory — AMS Slot Assignment** — Assign inventory spools to AMS slots for filament tracking. Hover over any non-Bambu-Lab AMS slot to assign or unassign spools. The assign modal filters out Bambu Lab spools (tracked via RFID) and spools already assigned to other slots. Bambu Lab spool slots automatically hide assign/unassign UI since they are managed by the AMS. When a Bambu Lab spool is inserted into a slot with a manual assignment, the assignment is automatically unlinked.
 - **Spool Inventory — Remaining Weight Editing** — Edit the remaining filament weight when adding or editing a spool. The new "Remaining Weight" field in the Additional section shows current weight (label weight minus consumed) with a max reference. Edits are stored as `weight_used` internally.
 - **Spool Inventory — Remaining Weight Editing** — Edit the remaining filament weight when adding or editing a spool. The new "Remaining Weight" field in the Additional section shows current weight (label weight minus consumed) with a max reference. Edits are stored as `weight_used` internally.
 - **Spool Inventory — Unified 3MF-Based Usage Tracking** ([#336](https://github.com/maziggy/bambuddy/issues/336)) — All spools (Bambu Lab and third-party) now use 3MF slicer estimates as the primary tracking source. Per-filament `used_g` data from the archived 3MF file provides precise per-spool consumption. For failed or aborted prints, per-layer G-code analysis provides accurate partial usage up to the exact failure layer, with linear progress scaling as fallback. AMS remain% delta is the final fallback for G-code-only prints without an archived 3MF. Slot-to-tray mapping uses queue `ams_mapping` for queue-initiated prints and the printer's `tray_now` state for single-filament non-queue prints, ensuring the correct physical spool is always tracked.
 - **Spool Inventory — Unified 3MF-Based Usage Tracking** ([#336](https://github.com/maziggy/bambuddy/issues/336)) — All spools (Bambu Lab and third-party) now use 3MF slicer estimates as the primary tracking source. Per-filament `used_g` data from the archived 3MF file provides precise per-spool consumption. For failed or aborted prints, per-layer G-code analysis provides accurate partial usage up to the exact failure layer, with linear progress scaling as fallback. AMS remain% delta is the final fallback for G-code-only prints without an archived 3MF. Slot-to-tray mapping uses queue `ams_mapping` for queue-initiated prints and the printer's `tray_now` state for single-filament non-queue prints, ensuring the correct physical spool is always tracked.
-- **Notification Templates — Filament Usage Variables** ([#336](https://github.com/maziggy/bambuddy/issues/336)) — `print_complete`, `print_failed`, and `print_stopped` notification events now expose `{filament_grams}` (total grams, scaled by progress for partial prints), `{filament_details}` (per-filament breakdown, e.g. "PLA: 10.0g | PETG: 5.0g"), and `{progress}` (completion percentage for failed/stopped prints). Webhook payloads include `filament_used`, `filament_details`, and `progress` fields. Per-slot filament data is stored in archive `extra_data` for downstream use.
+- **Notification Templates — Filament Usage Variables** ([#336](https://github.com/maziggy/bambuddy/issues/336)) — `print_complete`, `print_failed`, and `print_stopped` notification events now expose `{filament_grams}` (total grams, scaled by progress for partial prints), `{filament_details}` (per-filament breakdown with AMS slot info, e.g. "AMS-A T1 PLA: 12.4g | AMS-A T3 PETG: 2.8g"), and `{progress}` (completion percentage for failed/stopped prints). The `{filament_details}` variable includes the AMS unit and tray position for each filament used, with "Ext" shown for external spool holders. Falls back to type-only format (e.g. "PLA: 10.0g") when usage tracking data is unavailable. Webhook payloads include `filament_used`, `filament_details`, and `progress` fields. Per-slot filament data is stored in archive `extra_data` for downstream use.
 - **Printer Status Summary Bar — Next Available & Availability Count** ([#354](https://github.com/maziggy/bambuddy/issues/354)) — The status bar on the Printers page now shows an availability count ("X available") alongside the printing/offline counts, and a "Next available" indicator showing which printing printer will finish soonest — with printer name, mini progress bar, completion percentage, and remaining time. Useful for print farms to quickly identify the next free printer. Updates in real-time via WebSocket. Translated in all 4 locales (en, de, ja, it).
 - **Printer Status Summary Bar — Next Available & Availability Count** ([#354](https://github.com/maziggy/bambuddy/issues/354)) — The status bar on the Printers page now shows an availability count ("X available") alongside the printing/offline counts, and a "Next available" indicator showing which printing printer will finish soonest — with printer name, mini progress bar, completion percentage, and remaining time. Useful for print farms to quickly identify the next free printer. Updates in real-time via WebSocket. Translated in all 4 locales (en, de, ja, it).
 - **Nozzle-Aware AMS Filament Mapping for Dual-Nozzle Printers** ([#318](https://github.com/maziggy/bambuddy/issues/318)) — On dual-nozzle printers (H2D, H2D Pro), each AMS unit is physically connected to either the left or right nozzle. Bambuddy now reads nozzle assignments from the 3MF file (`filament_nozzle_map` + `physical_extruder_map` in `project_settings.config`) and constrains filament matching to only AMS trays connected to the correct nozzle via `ams_extruder_map`. Applies to the print scheduler, reprint modal, queue modal, and multi-printer selection. Falls back gracefully to unfiltered matching when no trays exist on the target nozzle. The filament mapping UI shows L/R nozzle badges for dual-nozzle prints. Translated in all 4 locales (en, de, ja, it).
 - **Nozzle-Aware AMS Filament Mapping for Dual-Nozzle Printers** ([#318](https://github.com/maziggy/bambuddy/issues/318)) — On dual-nozzle printers (H2D, H2D Pro), each AMS unit is physically connected to either the left or right nozzle. Bambuddy now reads nozzle assignments from the 3MF file (`filament_nozzle_map` + `physical_extruder_map` in `project_settings.config`) and constrains filament matching to only AMS trays connected to the correct nozzle via `ams_extruder_map`. Applies to the print scheduler, reprint modal, queue modal, and multi-printer selection. Falls back gracefully to unfiltered matching when no trays exist on the target nozzle. The filament mapping UI shows L/R nozzle badges for dual-nozzle prints. Translated in all 4 locales (en, de, ja, it).
 
 

+ 5 - 0
backend/app/main.py

@@ -2133,6 +2133,7 @@ async def on_print_complete(printer_id: int, data: dict):
     log_timing("Archive status update")
     log_timing("Archive status update")
 
 
     # Track filament consumption from AMS remain% deltas (skip if Spoolman handles usage)
     # Track filament consumption from AMS remain% deltas (skip if Spoolman handles usage)
+    usage_results: list[dict] = []
     try:
     try:
         async with async_session() as db:
         async with async_session() as db:
             from backend.app.api.routes.settings import get_setting
             from backend.app.api.routes.settings import get_setting
@@ -2363,6 +2364,10 @@ async def on_print_complete(printer_id: int, data: dict):
                                 scale = max(0.0, min((data.get("progress") or 0) / 100.0, 1.0))
                                 scale = max(0.0, min((data.get("progress") or 0) / 100.0, 1.0))
                                 slots = [{**s, "used_g": round(s["used_g"] * scale, 1)} for s in slots]
                                 slots = [{**s, "used_g": round(s["used_g"] * scale, 1)} for s in slots]
                             archive_data["filament_slots"] = slots
                             archive_data["filament_slots"] = slots
+
+                        # Pass usage tracker results for AMS slot info in notifications
+                        if usage_results:
+                            archive_data["usage_results"] = usage_results
                         # Add finish photo URL and image bytes if available
                         # Add finish photo URL and image bytes if available
                         if finish_photo_filename:
                         if finish_photo_filename:
                             from backend.app.api.routes.settings import get_setting
                             from backend.app.api.routes.settings import get_setting

+ 3 - 3
backend/app/schemas/notification_template.py

@@ -94,7 +94,7 @@ SAMPLE_DATA: dict[str, dict[str, str]] = {
         "filename": "Benchy.3mf",
         "filename": "Benchy.3mf",
         "duration": "1h 18m",
         "duration": "1h 18m",
         "filament_grams": "15.2",
         "filament_grams": "15.2",
-        "filament_details": "PLA: 15.2g",
+        "filament_details": "AMS-A T1 PLA: 12.4g | AMS-A T3 PETG: 2.8g",
         "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_154800_abc12345.jpg",
         "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_154800_abc12345.jpg",
         "timestamp": "2024-01-15 15:48",
         "timestamp": "2024-01-15 15:48",
         "app_name": "Bambuddy",
         "app_name": "Bambuddy",
@@ -104,7 +104,7 @@ SAMPLE_DATA: dict[str, dict[str, str]] = {
         "filename": "Benchy.3mf",
         "filename": "Benchy.3mf",
         "duration": "0h 45m",
         "duration": "0h 45m",
         "filament_grams": "7.6",
         "filament_grams": "7.6",
-        "filament_details": "PLA: 7.6g",
+        "filament_details": "AMS-A T1 PLA: 7.6g",
         "progress": "50",
         "progress": "50",
         "reason": "Filament runout",
         "reason": "Filament runout",
         "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_151500_def67890.jpg",
         "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_151500_def67890.jpg",
@@ -116,7 +116,7 @@ SAMPLE_DATA: dict[str, dict[str, str]] = {
         "filename": "Benchy.3mf",
         "filename": "Benchy.3mf",
         "duration": "0h 30m",
         "duration": "0h 30m",
         "filament_grams": "4.6",
         "filament_grams": "4.6",
-        "filament_details": "PLA: 4.6g",
+        "filament_details": "AMS-A T2 PLA: 4.6g",
         "progress": "30",
         "progress": "30",
         "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_150000_ghi11223.jpg",
         "finish_photo_url": "/api/v1/archives/123/photos/finish_20240115_150000_ghi11223.jpg",
         "timestamp": "2024-01-15 15:00",
         "timestamp": "2024-01-15 15:00",

+ 15 - 2
backend/app/services/notification_service.py

@@ -728,8 +728,21 @@ class NotificationService:
             if archive_data.get("finish_photo_url"):
             if archive_data.get("finish_photo_url"):
                 variables["finish_photo_url"] = archive_data["finish_photo_url"]
                 variables["finish_photo_url"] = archive_data["finish_photo_url"]
 
 
-            # Build per-slot breakdown string
-            if archive_data.get("filament_slots"):
+            # Build per-slot breakdown string with AMS info when available
+            if archive_data.get("usage_results"):
+                parts = []
+                for u in archive_data["usage_results"]:
+                    ams_id = u.get("ams_id", 0)
+                    tray_id = u.get("tray_id", 0)
+                    material = u.get("material", "Unknown") or "Unknown"
+                    used = u.get("weight_used", 0)
+                    if ams_id >= 128:
+                        slot_label = "Ext"
+                    else:
+                        slot_label = f"AMS-{chr(65 + ams_id)} T{tray_id + 1}"
+                    parts.append(f"{slot_label} {material}: {used:.1f}g")
+                variables["filament_details"] = " | ".join(parts)
+            elif archive_data.get("filament_slots"):
                 parts = []
                 parts = []
                 for slot in archive_data["filament_slots"]:
                 for slot in archive_data["filament_slots"]:
                     ftype = slot.get("type", "Unknown") or "Unknown"
                     ftype = slot.get("type", "Unknown") or "Unknown"

+ 2 - 0
backend/app/services/usage_tracker.py

@@ -184,6 +184,7 @@ async def on_print_complete(
                             "percent_used": delta_pct,
                             "percent_used": delta_pct,
                             "ams_id": ams_id,
                             "ams_id": ams_id,
                             "tray_id": tray_id,
                             "tray_id": tray_id,
+                            "material": spool.material,
                         }
                         }
                     )
                     )
 
 
@@ -384,6 +385,7 @@ async def _track_from_3mf(
                 "percent_used": percent,
                 "percent_used": percent,
                 "ams_id": ams_id,
                 "ams_id": ams_id,
                 "tray_id": tray_id,
                 "tray_id": tray_id,
+                "material": spool.material,
             }
             }
         )
         )