فهرست منبع

Fix AMS slot showing wrong material for "Support for" profiles

  Profiles like "PLA Support for PETG PETG Basic @Bambu Lab H2D" have
  filament_type PETG, but both the frontend and backend name parsers
  found "PLA" first (iterating material types in order). The MQTT command
  sent tray_type=PLA and tray_info_idx=GFL99 (PLA generic), so the
  slicer displayed PLA instead of PETG.

  - Frontend parsePresetName(): detect "X Support for Y" pattern, extract
    material after "Support for"
  - Frontend ConfigureAmsSlotModal: prefer corrected parsed material over
    stored localPreset.filament_type for tray_type, tray_info_idx, and
    temperature fallback
  - Backend _parse_material_from_name(): same "Support for" handling for
    future profile imports
  - Backend assign_spool: prioritize spool.material over lp.filament_type
    for generic filament ID lookup
maziggy 2 ماه پیش
والد
کامیت
330d77198a

+ 1 - 0
CHANGELOG.md

@@ -21,6 +21,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Inventory Scale Weight Check Column** — Added a "Weight Check" column (hidden by default) to the inventory table that compares each spool's last scale measurement against its calculated gross weight (net remaining + core weight). Spools within a ±50g tolerance show a green checkmark; mismatched spools show a yellow warning with the difference and a sync button that trusts the scale reading and resets weight tracking. The backend stores `last_scale_weight` and `last_weighed_at` on each spool whenever weight is synced via SpoolBuddy, and the column tooltip shows scale weight, calculated weight, and difference. Edge case: when scale weight is below core weight (empty spool or not on scale), the comparison treats it as a match since sync can't correct this.
 
 ### Fixed
+- **AMS Slot Shows Wrong Material for "Support for" Profiles** — Configuring an AMS slot with a filament profile like "PLA Support for PETG PETG Basic @Bambu Lab H2D 0.4 nozzle" set the slot material to PLA instead of PETG. The name parser iterated material types in order and returned the first match ("PLA"), ignoring that "PLA Support for PETG" means the filament type is PETG. Both the frontend `parsePresetName()` and backend `_parse_material_from_name()` now detect the "X Support for Y" naming pattern and extract the material after "Support for". The frontend also prefers the corrected parsed material over the stored `filament_type` (which may have been saved with the old parser during import).
 - **Spurious Error Notifications During Normal Printing (0300_0002)** — Some firmware versions send non-zero `print_error` values in MQTT during normal printing (e.g., `0x03000002` → short code `0300_0002`). The `print_error` parser treated any non-zero value as a real error, appending it to `hms_errors` and triggering notifications — even though the printer was printing fine. All known real HMS error codes have their low 16 bits >= `0x4000` (`0x4xxx` = fatal, `0x8xxx` = warning/pause, `0xCxxx` = prompt). Values below `0x4000` are status/phase indicators, not faults. Now skips `print_error` values where the error portion is below `0x4000`.
 - **K-Profile Apply Fails With Greenlet Error on Auto-Created Spools** — When a Bambu Lab spool was detected via RFID for the first time (auto-creating a new inventory entry), the K-profile application step logged `WARNING greenlet_spawn has not been called; can't call await_only() here`. The `create_spool_from_tray()` function flushed the new spool to the database but didn't eagerly load the `k_profiles` relationship. When `auto_assign_spool()` then iterated `spool.k_profiles` to find a matching K-profile, SQLAlchemy attempted a lazy load — which requires a synchronous DB call that's illegal inside an async context. The K-profile step was silently skipped (caught by `except Exception`), so spool assignment still worked but without K-profile selection. Now eagerly sets `k_profiles = []` on newly created spools since they can never have K-profiles yet.
 - **SpoolBuddy Link Tag Missing tag_type** — Linking an NFC tag to a spool via the SpoolBuddy dashboard's "Link to Spool" action only set `tag_uid` but left `tag_type` and `data_origin` empty, because it called the generic `updateSpool` API instead of the dedicated `linkTagToSpool` endpoint. The printer card's `LinkSpoolModal` already used `linkTagToSpool` correctly. Now uses `linkTagToSpool` with `tag_type: 'generic'` and `data_origin: 'nfc_link'`, which also handles conflict checks and archived tag recycling.

+ 1 - 1
backend/app/api/routes/inventory.py

@@ -824,7 +824,7 @@ async def assign_spool(
                         lp_result = await db.execute(select(LP).where(LP.id == local_id, LP.preset_type == "filament"))
                         lp = lp_result.scalar_one_or_none()
                         if lp:
-                            mat = (lp.filament_type or spool.material or "").upper().strip()
+                            mat = (spool.material or lp.filament_type or "").upper().strip()
                             tray_info_idx = (
                                 _GENERIC_IDS.get(mat) or _GENERIC_IDS.get(mat.split("-")[0].split(" ")[0]) or ""
                             )

+ 14 - 1
backend/app/services/orca_profiles.py

@@ -230,10 +230,23 @@ MATERIAL_TYPES = [
 
 
 def _parse_material_from_name(name: str) -> str | None:
-    """Extract filament material type from preset name, e.g. 'Overture PLA Matte' -> 'PLA'."""
+    """Extract filament material type from preset name, e.g. 'Overture PLA Matte' -> 'PLA'.
+
+    Handles 'X Support for Y' patterns where the filament type is Y, not X.
+    e.g. 'PLA Support for PETG PETG Basic @Bambu Lab H2D' -> 'PETG'.
+    """
     import re
 
     upper = name.upper()
+
+    # Handle "X Support for Y" pattern: the filament type is Y, not X.
+    support_match = re.search(r"\bSUPPORT\s+FOR\s+", upper)
+    if support_match:
+        after_support = upper[support_match.end() :]
+        for mat in MATERIAL_TYPES:
+            if re.search(rf"\b{mat}\b", after_support):
+                return mat
+
     for mat in MATERIAL_TYPES:
         if re.search(rf"\b{mat}\b", upper):
             return mat

+ 8 - 0
backend/tests/unit/test_orca_profiles.py

@@ -194,6 +194,14 @@ class TestParseMaterialFromName:
         assert _parse_material_from_name("Fiberlogy PA12+CF15") is None
         assert _parse_material_from_name("Fiberlogy PA @BBL X1C") == "PA"
 
+    def test_support_for_pattern(self):
+        from backend.app.services.orca_profiles import _parse_material_from_name
+
+        # "PLA Support for PETG" — filament type is PETG, not PLA
+        assert _parse_material_from_name("PLA Support for PETG PETG Basic @Bambu Lab H2D 0.4 nozzle") == "PETG"
+        assert _parse_material_from_name("PLA Support for ABS @BBL X1C") == "ABS"
+        assert _parse_material_from_name("PVA Support for PLA @BBL X1C") == "PLA"
+
 
 class TestParseVendorFromName:
     """Tests for _parse_vendor_from_name()."""

+ 27 - 6
frontend/src/components/ConfigureAmsSlotModal.tsx

@@ -87,9 +87,23 @@ const MATERIAL_TYPES = ['PLA', 'PETG', 'PCTG', 'ABS', 'ASA', 'TPU', 'PC', 'PA',
 function parsePresetName(name: string): { material: string; brand: string; variant: string } {
   // Remove printer/nozzle suffix first
   const withoutSuffix = name.replace(/@.+$/, '').trim();
+  const upperName = withoutSuffix.toUpperCase();
+
+  // Handle "X Support for Y" pattern: the filament type is Y, not X.
+  // e.g. "PLA Support for PETG PETG Basic" → material is PETG
+  const supportMatch = upperName.match(/\bSUPPORT\s+FOR\s+/);
+  if (supportMatch) {
+    const afterSupport = upperName.slice(supportMatch.index! + supportMatch[0].length);
+    for (const mat of MATERIAL_TYPES) {
+      const regex = new RegExp(`\\b${mat}\\b`);
+      if (regex.test(afterSupport)) {
+        const brand = withoutSuffix.slice(0, supportMatch.index).trim();
+        return { material: mat, brand, variant: 'Support' };
+      }
+    }
+  }
 
   // Try to find a known material type in the name
-  const upperName = withoutSuffix.toUpperCase();
   for (const mat of MATERIAL_TYPES) {
     // Use word boundary to match whole words only
     const regex = new RegExp(`\\b${mat}\\b`, 'i');
@@ -323,11 +337,15 @@ export function ConfigureAmsSlotModal({
       let trayInfoIdx: string;
       let settingId: string;
 
+      // Parsed material from preset name — handles "Support for" patterns correctly.
+      // Prefer this over stored filament_type which may have been parsed with old logic.
+      const parsedMat = parsed.material.toUpperCase();
+
       if (isLocal) {
         // Local presets have no Bambu Cloud setting_id, but need a valid
         // tray_info_idx for the printer to recognize the filament type.
         // Map the material type to the closest generic Bambu filament ID.
-        const material = (localPreset?.filament_type || parsed.material || '').toUpperCase();
+        const material = (MATERIAL_TYPES.includes(parsedMat) ? parsedMat : localPreset?.filament_type || parsed.material || '').toUpperCase();
         const GENERIC_IDS: Record<string, string> = {
           'PLA': 'GFL99', 'PLA-CF': 'GFL98', 'PLA SILK': 'GFL96', 'PLA HIGH SPEED': 'GFL95',
           'PETG': 'GFG99', 'PETG HF': 'GFG96', 'PETG-CF': 'GFG98', 'PCTG': 'GFG97',
@@ -375,8 +393,10 @@ export function ConfigureAmsSlotModal({
       let tempMax = isLocal && localPreset?.nozzle_temp_max ? localPreset.nozzle_temp_max : 230;
 
       if (!isLocal || isBuiltin || (!localPreset?.nozzle_temp_min && !localPreset?.nozzle_temp_max)) {
-        // Fall back to material-based defaults
-        const material = (isLocal ? (localPreset?.filament_type || parsed.material) : parsed.material).toUpperCase();
+        // Fall back to material-based defaults (prefer parsed material for "Support for" handling)
+        const material = (isLocal
+          ? (MATERIAL_TYPES.includes(parsedMat) ? parsedMat : localPreset?.filament_type || parsed.material || '')
+          : parsed.material).toUpperCase();
         if (material.includes('PLA')) {
           tempMin = 190;
           tempMax = 230;
@@ -407,9 +427,10 @@ export function ConfigureAmsSlotModal({
       // Parse K value from selected profile
       const kValue = selectedKProfile?.k_value ? parseFloat(selectedKProfile.k_value) : 0;
 
-      // Determine tray_type: use local preset's filament_type or parsed material
+      // Determine tray_type: prefer parsed material from preset name (handles "Support for"
+      // patterns correctly) over stored filament_type which may have been parsed with old logic.
       const trayType = isLocal
-        ? (localPreset?.filament_type || parsed.material || 'PLA')
+        ? (MATERIAL_TYPES.includes(parsedMat) ? parsedMat : localPreset?.filament_type || parsed.material || 'PLA')
         : (parsed.material || 'PLA');
 
       // Configure the slot via MQTT

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-D4k_r1QT.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BYJvXySm.js"></script>
+    <script type="module" crossorigin src="/assets/index-D4k_r1QT.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-D5I4wfky.css">
   </head>
   <body>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است