Просмотр исходного кода

Map the P-series model codes to the right printers

C11 is the P1P, C12 the P1S, C13 the X1E and N7 the P2S, as the virtual
printer and a real P1P 3MF already say. The frontend map had them shifted,
so discovery pre-filled a P1S as a P1P and an X1E as a P2S. The backend
map read C11/C12 as X1C/X1 and lacked N7, the firmware check sent C13 to
the P2S line, and the capability lists never matched BL-P001 because
their lookup strips the dash.

-----

Post work PR #3134
maziggy 2 дней назад
Родитель
Сommit
ad3b5f1982

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
CHANGELOG.md


+ 1 - 1
backend/app/services/firmware_check.py

@@ -84,7 +84,7 @@ MODEL_TO_API_KEY = {
     "BL-P003": "x1e",
     "C11": "p1",
     "C12": "p1",
-    "C13": "p2s",
+    "C13": "x1e",
     "N2S": "a1",
     "N1": "a1-mini",
     "N6": "x2d",

+ 21 - 9
backend/app/utils/printer_models.py

@@ -27,16 +27,22 @@ PRINTER_MODEL_MAP = {
 }
 
 # Map from printer_model_id (internal codes in slice_info.config) to short names
-# These are the codes Bambu Studio uses internally
+# These are the codes Bambu Studio uses internally, and the same codes the
+# printers announce over SSDP (DevModel), so Printer.model can hold them too.
+# A real P1P 3MF carries printer_model_id "C11" next to "Bambu Lab P1P"; the
+# X1 Carbon's code is "BL-P001", not a C-code.
 PRINTER_MODEL_ID_MAP = {
     # X1 series
-    "C11": "X1C",
-    "C12": "X1",
+    "BL-P001": "X1C",
+    "BL-P002": "X1",
     "C13": "X1E",
     # P1 series
+    "C11": "P1P",
+    "C12": "P1S",
     "P1P": "P1P",
     "P1S": "P1S",
     # P2 series
+    "N7": "P2S",
     "P2S": "P2S",
     # X2 series
     "N6": "X2D",
@@ -71,10 +77,12 @@ CARBON_ROD_MODELS = frozenset(
         "X1E",
         "P1P",
         "P1S",
-        # Internal codes
-        "C11",  # X1C
-        "C12",  # X1
+        # Internal codes (dashes stripped, as the lookup strips them)
+        "BLP001",  # X1C (BL-P001)
+        "BLP002",  # X1 (BL-P002)
         "C13",  # X1E
+        "C11",  # P1P
+        "C12",  # P1S
     ]
 )
 
@@ -176,6 +184,9 @@ NO_REMOTE_STORAGE_TOGGLE_MODELS = frozenset(
         # Display names (uppercase, no spaces)
         "P1S",
         "P1P",
+        # Internal codes
+        "C11",  # P1P
+        "C12",  # P1S
     ]
 )
 
@@ -194,11 +205,12 @@ ETHERNET_MODELS = frozenset(
         "H2DPRO",
         "H2C",
         "H2S",
-        # Internal codes
-        "C11",  # X1C
+        # Internal codes (dashes stripped, as the lookup strips them)
+        "BLP001",  # X1C (BL-P001)
         "C13",  # X1E
         "N6",  # X2D
-        "P1S",  # P1S
+        "C12",  # P1S
+        "N7",  # P2S
         "O1D",  # H2D
         "O1E",  # H2D Pro
         "O2D",  # H2D Pro (alternate)

+ 1 - 1
backend/tests/integration/test_library_slice_api.py

@@ -1424,7 +1424,7 @@ class TestSliceArchiveResliceModel:
         def handler(request: httpx.Request) -> httpx.Response:
             return httpx.Response(
                 status_code=200,
-                content=_make_sliced_3mf("C11"),  # X1C — same model as source
+                content=_make_sliced_3mf("BL-P001"),  # X1C — same model as source
                 headers={
                     "x-print-time-seconds": "600",
                     "x-filament-used-g": "5.0",

+ 65 - 0
backend/tests/unit/test_printer_model_codes.py

@@ -0,0 +1,65 @@
+"""Bambu internal model codes resolve to the right printer everywhere.
+
+The codes are shared by the SSDP DevModel header (so ``Printer.model`` of an
+older install can hold them) and slice_info's ``printer_model_id``. A real P1P
+3MF carries ``C11`` next to "Bambu Lab P1P", and the X1 Carbon's code is
+``BL-P001``. Several maps had the C-codes shifted onto the wrong printers:
+C11/C12 read as X1C/X1, the X1E's C13 checked P2S firmware, and the P2S's N7
+was missing, which left a P2S file's ``sliced_for_model`` as the raw code.
+"""
+
+import pytest
+
+from backend.app.services.firmware_check import FirmwareCheckService
+from backend.app.utils.printer_models import (
+    get_rod_type,
+    has_ethernet,
+    has_remote_storage_toggle,
+    is_gcode_compatible,
+    normalize_printer_model_id,
+)
+
+CODES = [
+    ("BL-P001", "X1C"),
+    ("BL-P002", "X1"),
+    ("C13", "X1E"),
+    ("C11", "P1P"),
+    ("C12", "P1S"),
+    ("N7", "P2S"),
+    ("N6", "X2D"),
+    ("O1D", "H2D"),
+    ("N2S", "A1"),
+    ("N1", "A1 Mini"),
+]
+
+
+@pytest.mark.parametrize(("code", "name"), CODES)
+def test_code_resolves_to_its_printer(code, name):
+    assert normalize_printer_model_id(code) == name
+
+
+@pytest.mark.parametrize(("code", "name"), CODES)
+def test_capabilities_agree_between_code_and_name(code, name):
+    assert has_ethernet(code) == has_ethernet(name)
+    assert get_rod_type(code) == get_rod_type(name)
+    assert has_remote_storage_toggle(code) == has_remote_storage_toggle(name)
+
+
+def test_the_p1p_has_no_ethernet_and_the_x1c_has():
+    assert not has_ethernet("C11")
+    assert has_ethernet("BL-P001")
+
+
+def test_a_p2s_file_can_go_to_a_p2s():
+    # A 3MF without printer_model in project_settings keeps the resolved
+    # printer_model_id as sliced_for_model; for N7 that used to be "N7",
+    # which the dispatch gate compared against "P2S" and refused.
+    assert is_gcode_compatible(normalize_printer_model_id("N7"), "P2S")
+
+
+@pytest.mark.parametrize(
+    ("model", "key"),
+    [("C13", "x1e"), ("X1E", "x1e"), ("C11", "p1"), ("C12", "p1"), ("N7", "p2s"), ("BL-P001", "x1")],
+)
+def test_firmware_line_for_code(model, key):
+    assert FirmwareCheckService()._resolve_api_key(model) == key

+ 4 - 2
backend/tests/unit/test_scheduler_model_mismatch.py

@@ -72,9 +72,11 @@ def test_normalization_spaces_dashes_case():
 
 def test_internal_codes_resolve_to_short_names():
     # slice_info printer_model_id codes compare equal to their short names.
-    assert is_gcode_compatible("C11", "X1C")
+    assert is_gcode_compatible("BL-P001", "X1C")
+    assert is_gcode_compatible("C11", "P1P")
+    assert is_gcode_compatible("N7", "P2S")
     assert is_gcode_compatible("O1D", "H2D")
-    assert is_gcode_compatible("C11", "P1S")  # X1C → family with P1S
+    assert is_gcode_compatible("C11", "X1C")  # P1P → family with X1C
     assert not is_gcode_compatible("C11", "H2D")
 
 

+ 35 - 0
frontend/src/__tests__/utils/printerModel.test.ts

@@ -0,0 +1,35 @@
+import { describe, it, expect } from 'vitest';
+import { mapModelCode } from '../../utils/printerModel';
+
+describe('mapModelCode', () => {
+  // A real P1P 3MF carries C11 next to "Bambu Lab P1P"; the X1 Carbon's code
+  // is BL-P001. The C-codes used to be shifted onto the wrong printers, and a
+  // discovered printer is saved under the mapped name.
+  it.each([
+    ['BL-P001', 'X1C'],
+    ['BL-P002', 'X1'],
+    ['C13', 'X1E'],
+    ['C11', 'P1P'],
+    ['C12', 'P1S'],
+    ['N7', 'P2S'],
+    ['N6', 'X2D'],
+    ['O1D', 'H2D'],
+    ['N2S', 'A1'],
+    ['N1', 'A1 Mini'],
+    ['A11', 'A1'],
+    ['A12', 'A1 Mini'],
+    ['A04', 'A1 Mini'],
+  ])('maps %s to %s', (code, name) => {
+    expect(mapModelCode(code)).toBe(name);
+  });
+
+  it('keeps display names and unknown values as they are', () => {
+    expect(mapModelCode('P1S')).toBe('P1S');
+    expect(mapModelCode('Future printer')).toBe('Future printer');
+  });
+
+  it('returns an empty string for a missing model', () => {
+    expect(mapModelCode(null)).toBe('');
+    expect(mapModelCode('')).toBe('');
+  });
+});

+ 9 - 4
frontend/src/utils/printerModel.ts

@@ -13,17 +13,22 @@ export function mapModelCode(ssdpModel: string | null): string {
     'BL-P001': 'X1C',
     'BL-P002': 'X1',
     'BL-P003': 'X1E',
+    'C13': 'X1E',
     // X2 Series
     'N6': 'X2D',
     // A2 Series
     'N9': 'A2L',
-    // P Series
-    'C11': 'P1S',
-    'C12': 'P1P',
-    'C13': 'P2S',
+    // P Series. A real P1P 3MF carries C11 next to "Bambu Lab P1P"; the
+    // backend's PRINTER_MODEL_ID_MAP and the virtual printer use the same codes.
+    'C11': 'P1P',
+    'C12': 'P1S',
+    'N7': 'P2S',
     // A1 Series
     'N2S': 'A1',
     'N1': 'A1 Mini',
+    'A11': 'A1',
+    'A12': 'A1 Mini',
+    'A04': 'A1 Mini',
     // Direct matches
     'X1C': 'X1C',
     'X1': 'X1',

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/ImagePreviewModal-DFDoPuB5.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
static/assets/PdfPreviewModal-D0TSAN3-.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/SpreadsheetPreviewModal-CVZij-ds.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
static/assets/index-Besr7Tb4.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/pdf-FNB19a5J.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-hZxr41RS.js"></script>
+    <script type="module" crossorigin src="/assets/index-Besr7Tb4.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/chunk-aKtaBQYM.js">
     <link rel="stylesheet" crossorigin href="/assets/index-2v_Z1nnP.css">
   </head>

Некоторые файлы не были показаны из-за большого количества измененных файлов