Browse Source

feat(slice): cross-printer re-slicing — drop the gate, the banner, and the dead plumbing

  Step 0 empirical test on 2026-05-20 disproved the "CLI cannot re-slice a
  3MF for a different printer" assumption: feeding an 18-color H2D-bound
  Trent900.3mf to the X1C bundle via /slice produces valid X1C G-code in
  1.8s, with bed (256x256), kinematics, nozzle count, machine_start_gcode,
  and bed_exclude_area all coming from the target bundle.

  - SliceModal: drop !printerMismatch from isReady; remove the banner and
    the sourcePrinterModel / printerProfileName / printerMismatch state
    entirely. Cross-printer slicing is now indistinguishable from a normal
    slice; the picker already shows the target printer.
  - Remove slice.printerMismatch from all 8 locales.
  - API cleanup: drop source_printer_model from /library/files/{id}/plates
    and /archives/{id}/plates responses, drop the field from
    frontend/src/types/plates.ts (PlateMetadata + LibraryFilePlatesResponse),
    delete extract_source_printer_model_from_3mf from threemf_tools.py and
    its 6 unit tests. Zero remaining consumers.
  - i18n discipline cleanup in SliceModal.tsx (same drop): strip every
    inline English defaultValue / positional fallback from t() calls (22
    sites). Add slice.bundle / slice.bundleNone / slice.bundleAllRequired
    to all 8 locales — they had no entry in any locale file and were being
    served from the inline English fallback for every non-English user.
  - Tests: rewrite the mismatch-warning test to assert "no banner, Slice
    enabled" when models differ (regression guard); delete 2 obsolete
    tests covering gate states that no longer exist.
maziggy 1 week ago
parent
commit
ed27b27adb

File diff suppressed because it is too large
+ 1 - 0
CHANGELOG.md


+ 0 - 9
backend/app/api/routes/archives.py

@@ -33,7 +33,6 @@ from backend.app.utils.http import build_content_disposition
 from backend.app.utils.threemf_tools import (
 from backend.app.utils.threemf_tools import (
     extract_nozzle_mapping_from_3mf,
     extract_nozzle_mapping_from_3mf,
     extract_project_filaments_from_3mf,
     extract_project_filaments_from_3mf,
-    extract_source_printer_model_from_3mf,
 )
 )
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -3316,20 +3315,12 @@ async def get_archive_plates(
     # to preview gcode — the viewer, skip-objects — can gate on this instead of
     # to preview gcode — the viewer, skip-objects — can gate on this instead of
     # 404-ing on every plate request.
     # 404-ing on every plate request.
     has_gcode = bool(gcode_files)
     has_gcode = bool(gcode_files)
-    # SliceModal pre-check signal — see library.py for rationale.
-    source_printer_model: str | None = None
-    try:
-        with zipfile.ZipFile(file_path, "r") as zf:
-            source_printer_model = extract_source_printer_model_from_3mf(zf)
-    except (zipfile.BadZipFile, OSError):
-        pass
     return {
     return {
         "archive_id": archive_id,
         "archive_id": archive_id,
         "filename": archive.filename,
         "filename": archive.filename,
         "plates": plates,
         "plates": plates,
         "is_multi_plate": len(plates) > 1,
         "is_multi_plate": len(plates) > 1,
         "has_gcode": has_gcode,
         "has_gcode": has_gcode,
-        "source_printer_model": source_printer_model,
     }
     }
 
 
 
 

+ 0 - 12
backend/app/api/routes/library.py

@@ -67,7 +67,6 @@ from backend.app.services.stl_thumbnail import generate_stl_thumbnail
 from backend.app.utils.threemf_tools import (
 from backend.app.utils.threemf_tools import (
     extract_nozzle_mapping_from_3mf,
     extract_nozzle_mapping_from_3mf,
     extract_project_filaments_from_3mf,
     extract_project_filaments_from_3mf,
-    extract_source_printer_model_from_3mf,
 )
 )
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -2419,22 +2418,11 @@ async def get_library_file_plates(
     except Exception as e:
     except Exception as e:
         logger.warning("Failed to parse plates from library file %s: %s", file_id, e)
         logger.warning("Failed to parse plates from library file %s: %s", file_id, e)
 
 
-    # SliceModal pre-check signal: the source 3MF's bound printer model. The
-    # CLI cannot re-slice for a different printer; surface this so the modal
-    # can warn the user before they pick a mismatched profile.
-    source_printer_model: str | None = None
-    try:
-        with zipfile.ZipFile(file_path, "r") as zf:
-            source_printer_model = extract_source_printer_model_from_3mf(zf)
-    except (zipfile.BadZipFile, OSError):
-        pass
-
     return {
     return {
         "file_id": file_id,
         "file_id": file_id,
         "filename": lib_file.filename,
         "filename": lib_file.filename,
         "plates": plates,
         "plates": plates,
         "is_multi_plate": len(plates) > 1,
         "is_multi_plate": len(plates) > 1,
-        "source_printer_model": source_printer_model,
     }
     }
 
 
 
 

+ 0 - 32
backend/app/utils/threemf_tools.py

@@ -609,38 +609,6 @@ def inject_gcode_into_3mf(
         return None
         return None
 
 
 
 
-def extract_source_printer_model_from_3mf(zf: zipfile.ZipFile) -> str | None:
-    """Source 3MF's bound printer model from ``Metadata/project_settings.config``.
-
-    Returns e.g. ``"Bambu Lab A1"`` when the project was built for an A1, or
-    ``None`` when the file lacks the metadata or the field is absent. The
-    SliceModal uses this to warn the user before slicing if the chosen
-    printer profile targets a different model — the slicer CLI rejects
-    cross-printer slicing with rc=-16 and the result, when the strip + load
-    fallback masks it, is a misleadingly-tagged archive.
-    """
-    if "Metadata/project_settings.config" not in zf.namelist():
-        return None
-    try:
-        proj = json.loads(zf.read("Metadata/project_settings.config").decode())
-    except (ValueError, OSError):
-        return None
-    if not isinstance(proj, dict):
-        return None
-    model = proj.get("printer_model")
-    if isinstance(model, str) and model.strip():
-        return model.strip()
-    # Some older Bambu Studio exports stored the model under
-    # ``printer_settings_id`` (e.g. "Bambu Lab A1 0.4 nozzle"); strip the
-    # nozzle suffix to get the canonical model name. Best-effort — if the
-    # field doesn't follow the convention we leave it as-is.
-    settings_id = proj.get("printer_settings_id")
-    if isinstance(settings_id, str) and settings_id.strip():
-        # Drop trailing " 0.4 nozzle" / " 0.2 nozzle" / etc.
-        return re.sub(r"\s+0\.\d+\s+nozzle$", "", settings_id.strip())
-    return None
-
-
 def extract_project_filaments_from_3mf(zf: zipfile.ZipFile) -> list[dict]:
 def extract_project_filaments_from_3mf(zf: zipfile.ZipFile) -> list[dict]:
     """Project-wide AMS slot config from ``Metadata/project_settings.config``.
     """Project-wide AMS slot config from ``Metadata/project_settings.config``.
 
 

+ 0 - 41
backend/tests/unit/test_threemf_tools.py

@@ -13,7 +13,6 @@ from backend.app.utils.threemf_tools import (
     extract_filament_usage_from_3mf,
     extract_filament_usage_from_3mf,
     extract_plate_extruder_set_from_3mf,
     extract_plate_extruder_set_from_3mf,
     extract_project_filaments_from_3mf,
     extract_project_filaments_from_3mf,
-    extract_source_printer_model_from_3mf,
     get_cumulative_usage_at_layer,
     get_cumulative_usage_at_layer,
     mm_to_grams,
     mm_to_grams,
     parse_gcode_layer_filament_usage,
     parse_gcode_layer_filament_usage,
@@ -646,43 +645,3 @@ class TestExtractPlateExtruderSetFrom3mf:
             # Top-level metadata still works; missing component model file
             # Top-level metadata still works; missing component model file
             # is silently skipped without crashing.
             # is silently skipped without crashing.
             assert extract_plate_extruder_set_from_3mf(zf, plate_id=1) == {2}
             assert extract_plate_extruder_set_from_3mf(zf, plate_id=1) == {2}
-
-
-# ---------------------------------------------------------------------------
-# Tests for extract_source_printer_model_from_3mf — feeds the SliceModal's
-# pre-slice mismatch warning. The CLI cannot re-slice a 3MF for a different
-# printer, so warning the user up-front avoids producing wrong-printer output.
-# ---------------------------------------------------------------------------
-
-
-class TestExtractSourcePrinterModelFrom3mf:
-    def test_returns_none_when_project_settings_missing(self):
-        with _make_3mf_with({"placeholder.txt": "hi"}) as zf:
-            assert extract_source_printer_model_from_3mf(zf) is None
-
-    def test_reads_printer_model_directly(self):
-        proj = {"printer_model": "Bambu Lab H2D"}
-        with _make_3mf_with({"Metadata/project_settings.config": json.dumps(proj)}) as zf:
-            assert extract_source_printer_model_from_3mf(zf) == "Bambu Lab H2D"
-
-    def test_falls_back_to_printer_settings_id_with_nozzle_strip(self):
-        # Older Bambu Studio exports stored the printer under
-        # printer_settings_id, often with a "0.4 nozzle" suffix the helper
-        # must strip to match the canonical model name.
-        proj = {"printer_settings_id": "Bambu Lab A1 0.4 nozzle"}
-        with _make_3mf_with({"Metadata/project_settings.config": json.dumps(proj)}) as zf:
-            assert extract_source_printer_model_from_3mf(zf) == "Bambu Lab A1"
-
-    def test_settings_id_without_nozzle_suffix_returned_as_is(self):
-        proj = {"printer_settings_id": "Bambu Lab P1S"}
-        with _make_3mf_with({"Metadata/project_settings.config": json.dumps(proj)}) as zf:
-            assert extract_source_printer_model_from_3mf(zf) == "Bambu Lab P1S"
-
-    def test_corrupt_json_returns_none_no_exception(self):
-        with _make_3mf_with({"Metadata/project_settings.config": b"{broken"}) as zf:
-            assert extract_source_printer_model_from_3mf(zf) is None
-
-    def test_empty_string_treated_as_missing(self):
-        proj = {"printer_model": "", "printer_settings_id": ""}
-        with _make_3mf_with({"Metadata/project_settings.config": json.dumps(proj)}) as zf:
-            assert extract_source_printer_model_from_3mf(zf) is None

+ 7 - 88
frontend/src/__tests__/components/SliceModal.test.tsx

@@ -764,18 +764,16 @@ describe('SliceModal', () => {
     });
     });
   });
   });
 
 
-  // Pre-slice printer-mismatch warning. The slicer CLI cannot re-slice a
-  // 3MF for a different printer model — clicking Slice in that state
-  // would silently fall back to the embedded settings and produce a
-  // wrong-printer file. The modal surfaces a warning and disables Slice
-  // when the source's source_printer_model doesn't match the picked
-  // printer profile.
-  it('shows a printer-mismatch warning and disables Slice when models differ', async () => {
+  // Cross-printer re-slicing is a normal, supported operation as of
+  // 2026-05-20 (Step 0 empirical test: sidecar overrides printer / process
+  // / bed / kinematics from the picked bundle, producing valid target-
+  // printer G-code). No banner, no warning — the picker UI already shows
+  // which printer the user picked, and that's enough.
+  it('does not surface any cross-printer banner and keeps Slice enabled when models differ', async () => {
     mockApi.getLibraryFilePlates.mockResolvedValue({
     mockApi.getLibraryFilePlates.mockResolvedValue({
       file_id: 100,
       file_id: 100,
       filename: 'A1Original.3mf',
       filename: 'A1Original.3mf',
       is_multi_plate: false,
       is_multi_plate: false,
-      source_printer_model: 'A1',
       plates: [
       plates: [
         {
         {
           index: 1,
           index: 1,
@@ -807,86 +805,7 @@ describe('SliceModal', () => {
       expect(screen.getByText('Bambu Lab X1 Carbon 0.4 nozzle')).toBeDefined(),
       expect(screen.getByText('Bambu Lab X1 Carbon 0.4 nozzle')).toBeDefined(),
     );
     );
 
 
-    // Warning banner is visible (role=alert) and references both models.
-    const alert = await screen.findByRole('alert');
-    expect(alert.textContent).toMatch(/A1/);
-    expect(alert.textContent).toMatch(/X1 Carbon/);
-
-    // Slice button is disabled while the warning is up.
-    const sliceButton = screen.getByRole('button', { name: /^Slice$/ }) as HTMLButtonElement;
-    expect(sliceButton.disabled).toBe(true);
-  });
-
-  it('keeps Slice enabled when the picked profile matches the source printer model', async () => {
-    mockApi.getLibraryFilePlates.mockResolvedValue({
-      file_id: 100,
-      filename: 'X1COriginal.3mf',
-      is_multi_plate: false,
-      source_printer_model: 'X1 Carbon',
-      plates: [
-        {
-          index: 1,
-          name: 'Plate 1',
-          objects: [],
-          has_thumbnail: false,
-          thumbnail_url: null,
-          print_time_seconds: null,
-          filament_used_grams: null,
-          filaments: [],
-        },
-      ],
-    });
-    mockApi.getSlicerPresets.mockResolvedValue(makeUnified({
-      standard: {
-        printer: [{ id: 'Bambu Lab X1 Carbon 0.4 nozzle', name: 'Bambu Lab X1 Carbon 0.4 nozzle', source: 'standard' }],
-        process: [{ id: '0.20mm Standard', name: '0.20mm Standard', source: 'standard' }],
-        filament: [{ id: 'Bambu PLA Basic', name: 'Bambu PLA Basic', source: 'standard' }],
-      },
-    }));
-
-    renderWithTracker({
-      source: { kind: 'libraryFile', id: 100, filename: 'X1COriginal.3mf' },
-      onClose: vi.fn(),
-    });
-
-    await waitFor(() =>
-      expect(screen.getByText('Bambu Lab X1 Carbon 0.4 nozzle')).toBeDefined(),
-    );
-
-    // No mismatch warning.
-    expect(screen.queryByRole('alert')).toBeNull();
-    const sliceButton = screen.getByRole('button', { name: /^Slice$/ }) as HTMLButtonElement;
-    expect(sliceButton.disabled).toBe(false);
-  });
-
-  it('keeps Slice enabled when source_printer_model is unknown (legacy archives)', async () => {
-    // Older 3MFs without project_settings.printer_model fall through to
-    // no-warning — we don't have enough info to gate the user.
-    mockApi.getLibraryFilePlates.mockResolvedValue({
-      file_id: 100,
-      filename: 'Legacy.3mf',
-      is_multi_plate: false,
-      source_printer_model: null,
-      plates: [
-        {
-          index: 1,
-          name: 'Plate 1',
-          objects: [],
-          has_thumbnail: false,
-          thumbnail_url: null,
-          print_time_seconds: null,
-          filament_used_grams: null,
-          filaments: [],
-        },
-      ],
-    });
-
-    renderWithTracker({
-      source: { kind: 'libraryFile', id: 100, filename: 'Legacy.3mf' },
-      onClose: vi.fn(),
-    });
-
-    await waitFor(() => expect(screen.getByText('My Custom X1C')).toBeDefined());
+    // No banner, no alert — re-slicing across printers is just a normal slice now.
     expect(screen.queryByRole('alert')).toBeNull();
     expect(screen.queryByRole('alert')).toBeNull();
     const sliceButton = screen.getByRole('button', { name: /^Slice$/ }) as HTMLButtonElement;
     const sliceButton = screen.getByRole('button', { name: /^Slice$/ }) as HTMLButtonElement;
     expect(sliceButton.disabled).toBe(false);
     expect(sliceButton.disabled).toBe(false);

+ 31 - 82
frontend/src/components/SliceModal.tsx

@@ -189,7 +189,7 @@ function FilamentAnalysisSpinner({
     } else {
     } else {
       showPersistentToast(
       showPersistentToast(
         toastId,
         toastId,
-        t('slice.previewToast', 'Analyzing {{name}} — {{elapsed}}', {
+        t('slice.previewToast', {
           name: prettyName,
           name: prettyName,
           elapsed: elapsedStr,
           elapsed: elapsedStr,
         }),
         }),
@@ -206,7 +206,7 @@ function FilamentAnalysisSpinner({
   const inlineLabel =
   const inlineLabel =
     stage && typeof percent === 'number' && percent > 0
     stage && typeof percent === 'number' && percent > 0
       ? `${stage} (${Math.min(100, Math.max(0, Math.round(percent)))}%)`
       ? `${stage} (${Math.min(100, Math.max(0, Math.round(percent)))}%)`
-      : t('slice.analyzingPlateFilaments', 'Analyzing plate filaments…');
+      : t('slice.analyzingPlateFilaments');
   return (
   return (
     <div className="flex flex-col gap-1 text-bambu-gray text-sm py-2">
     <div className="flex flex-col gap-1 text-bambu-gray text-sm py-2">
       <div className="flex items-center gap-2">
       <div className="flex items-center gap-2">
@@ -425,7 +425,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
           bundleFilamentNames.length === 0 ||
           bundleFilamentNames.length === 0 ||
           bundleFilamentNames.some((n) => n == null)
           bundleFilamentNames.some((n) => n == null)
         ) {
         ) {
-          throw new Error(t('slice.bundleAllRequired', 'Bundle process and every filament slot must be picked'));
+          throw new Error(t('slice.bundleAllRequired'));
         }
         }
         const bundleSpec: SliceBundleSpec = {
         const bundleSpec: SliceBundleSpec = {
           bundle_id: selectedBundle.id,
           bundle_id: selectedBundle.id,
@@ -447,7 +447,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
           filamentPresets.length === 0 ||
           filamentPresets.length === 0 ||
           filamentPresets.some((r) => r == null)
           filamentPresets.some((r) => r == null)
         ) {
         ) {
-          throw new Error(t('slice.allPresetsRequired', 'All presets must be selected'));
+          throw new Error(t('slice.allPresetsRequired'));
         }
         }
         body = {
         body = {
           printer_preset: printerPreset,
           printer_preset: printerPreset,
@@ -480,55 +480,20 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
     },
     },
   });
   });
 
 
-  // Pre-slice compatibility check: the slicer CLI (both OrcaSlicer and
-  // BambuStudio) cannot re-slice a 3MF for a printer different from the one
-  // it was originally bound to — the cross-printer "convert project" flow
-  // is desktop-Studio only. If we can match the source's printer model to a
-  // SliceModal-known model and the user's chosen printer profile names a
-  // different model, surface a warning before they click Slice.
-  //
-  // For bundle mode, the bundle's printer_preset_name plays the same role
-  // as the picked PresetRef's resolved name in preset mode.
-  const sourcePrinterModel = platesQuery.data?.source_printer_model ?? null;
-  const printerProfileName = isBundleMode
-    ? selectedBundle?.printer_preset_name.replace(/^# /, '') ?? null
-    : printerPreset
-      ? presetsQuery.data?.[printerPreset.source].printer.find((p) => p.id === printerPreset.id)?.name
-      : null;
-  // Profile names follow `<model> <nozzle> nozzle` (e.g. "Bambu Lab H2D 0.4
-  // nozzle"). The CLI compat check uses the model prefix; substring match
-  // catches both standard and locally-imported user-named profiles that
-  // include the model in the name. Cloud presets with arbitrary names
-  // (e.g. "My Custom X1C") fall through to no-warning, which is a
-  // reasonable default — the user picked it knowingly.
-  const printerMismatch =
-    !!sourcePrinterModel &&
-    !!printerProfileName &&
-    !printerProfileName.toLowerCase().includes(sourcePrinterModel.toLowerCase());
-
-  // Slice button stays disabled until *all* of these hold:
-  //   - the preview slice / embedded-metadata read has succeeded so we know
-  //     the per-plate filament slot list is final
-  //     (filamentReqsQuery.isSuccess). Without this gate the synthetic
-  //     single-slot fallback would auto-enable the button on opaque
-  //     defaults, before the slicer has even returned the real slot map.
-  //   - printer + process picked, every filament slot has a profile (the
-  //     auto-pick fills these once filamentSlots arrives)
-  //   - no printer-mismatch warning is up (clicking would silently fall
-  //     back to embedded settings and produce a wrong-printer file)
+  // Slice button stays disabled until the preview slice / embedded-metadata
+  // read has succeeded (filamentReqsQuery.isSuccess) and every filament slot
+  // has a picked profile.
   const isReady = isBundleMode
   const isReady = isBundleMode
     ? selectedBundle != null &&
     ? selectedBundle != null &&
       bundleProcessName != null &&
       bundleProcessName != null &&
       filamentReqsQuery.isSuccess &&
       filamentReqsQuery.isSuccess &&
       bundleFilamentNames.length > 0 &&
       bundleFilamentNames.length > 0 &&
-      bundleFilamentNames.every((n) => n != null) &&
-      !printerMismatch
+      bundleFilamentNames.every((n) => n != null)
     : printerPreset != null &&
     : printerPreset != null &&
       processPreset != null &&
       processPreset != null &&
       filamentReqsQuery.isSuccess &&
       filamentReqsQuery.isSuccess &&
       filamentPresets.length > 0 &&
       filamentPresets.length > 0 &&
-      filamentPresets.every((r) => r != null) &&
-      !printerMismatch;
+      filamentPresets.every((r) => r != null);
   const isEnqueuing = enqueueMutation.isPending;
   const isEnqueuing = enqueueMutation.isPending;
 
 
   // Step 1: plate picker for multi-plate 3MF sources. Cancelling closes the
   // Step 1: plate picker for multi-plate 3MF sources. Cancelling closes the
@@ -563,7 +528,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
           <div className="min-w-0">
           <div className="min-w-0">
             <h3 className="text-white font-medium flex items-center gap-2">
             <h3 className="text-white font-medium flex items-center gap-2">
               <Cog className="w-4 h-4" />
               <Cog className="w-4 h-4" />
-              {t('slice.title', 'Slice model')}
+              {t('slice.title')}
             </h3>
             </h3>
             <p className="text-xs text-bambu-gray mt-1 truncate" title={source.filename}>
             <p className="text-xs text-bambu-gray mt-1 truncate" title={source.filename}>
               {source.filename}
               {source.filename}
@@ -576,7 +541,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
             onClick={onClose}
             onClick={onClose}
             disabled={isEnqueuing}
             disabled={isEnqueuing}
             className="flex-shrink-0 text-bambu-gray hover:text-white transition-colors disabled:opacity-50"
             className="flex-shrink-0 text-bambu-gray hover:text-white transition-colors disabled:opacity-50"
-            aria-label={t('common.close', 'Close')}
+            aria-label={t('common.close')}
           >
           >
             <X className="w-5 h-5" />
             <X className="w-5 h-5" />
           </button>
           </button>
@@ -590,7 +555,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
           {(platesQuery.isLoading || presetsQuery.isLoading) && (
           {(platesQuery.isLoading || presetsQuery.isLoading) && (
             <div className="flex items-center gap-2 text-bambu-gray text-sm">
             <div className="flex items-center gap-2 text-bambu-gray text-sm">
               <Loader2 className="w-4 h-4 animate-spin" />
               <Loader2 className="w-4 h-4 animate-spin" />
-              {t('slice.loadingPresets', 'Loading presets…')}
+              {t('slice.loadingPresets')}
             </div>
             </div>
           )}
           )}
 
 
@@ -624,7 +589,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
               {!isBundleMode && (
               {!isBundleMode && (
                 <>
                 <>
                   <PresetDropdown
                   <PresetDropdown
-                    label={t('slice.printer', 'Printer profile')}
+                    label={t('slice.printer')}
                     slot="printer"
                     slot="printer"
                     data={presetsQuery.data}
                     data={presetsQuery.data}
                     value={printerPreset}
                     value={printerPreset}
@@ -632,7 +597,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
                     disabled={isEnqueuing}
                     disabled={isEnqueuing}
                   />
                   />
                   <PresetDropdown
                   <PresetDropdown
-                    label={t('slice.process', 'Process profile')}
+                    label={t('slice.process')}
                     slot="process"
                     slot="process"
                     data={presetsQuery.data}
                     data={presetsQuery.data}
                     value={processPreset}
                     value={processPreset}
@@ -648,14 +613,14 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
                       verify the printer they're slicing for. */}
                       verify the printer they're slicing for. */}
                   <div>
                   <div>
                     <label className="block text-sm text-bambu-gray mb-1">
                     <label className="block text-sm text-bambu-gray mb-1">
-                      {t('slice.printer', 'Printer profile')}
+                      {t('slice.printer')}
                     </label>
                     </label>
                     <div className="px-3 py-2 rounded-md bg-bambu-dark/40 border border-bambu-dark-tertiary text-white text-sm">
                     <div className="px-3 py-2 rounded-md bg-bambu-dark/40 border border-bambu-dark-tertiary text-white text-sm">
                       {selectedBundle.printer_preset_name}
                       {selectedBundle.printer_preset_name}
                     </div>
                     </div>
                   </div>
                   </div>
                   <BundleStringDropdown
                   <BundleStringDropdown
-                    label={t('slice.process', 'Process profile')}
+                    label={t('slice.process')}
                     options={selectedBundle.process}
                     options={selectedBundle.process}
                     value={bundleProcessName}
                     value={bundleProcessName}
                     onChange={setBundleProcessName}
                     onChange={setBundleProcessName}
@@ -691,12 +656,11 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
                       ? t('slice.filamentSlot', {
                       ? t('slice.filamentSlot', {
                           index: idx + 1,
                           index: idx + 1,
                           type: slot.type,
                           type: slot.type,
-                          defaultValue: `Filament ${idx + 1} (${slot.type || ''})`,
                         })
                         })
-                      : t('slice.filament', 'Filament profile');
+                      : t('slice.filament');
                   const label = isUsed
                   const label = isUsed
                     ? baseLabel
                     ? baseLabel
-                    : `${baseLabel} ${t('slice.notUsedByPlate', '— not used by this plate')}`;
+                    : `${baseLabel} ${t('slice.notUsedByPlate')}`;
                   return (
                   return (
                     <BundleStringDropdown
                     <BundleStringDropdown
                       key={`bundle-filament-${idx}`}
                       key={`bundle-filament-${idx}`}
@@ -732,12 +696,11 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
                       ? t('slice.filamentSlot', {
                       ? t('slice.filamentSlot', {
                           index: idx + 1,
                           index: idx + 1,
                           type: slot.type,
                           type: slot.type,
-                          defaultValue: `Filament ${idx + 1} (${slot.type || ''})`,
                         })
                         })
-                      : t('slice.filament', 'Filament profile');
+                      : t('slice.filament');
                   const label = isUsed
                   const label = isUsed
                     ? baseLabel
                     ? baseLabel
-                    : `${baseLabel} ${t('slice.notUsedByPlate', '— not used by this plate')}`;
+                    : `${baseLabel} ${t('slice.notUsedByPlate')}`;
                   return (
                   return (
                     <PresetDropdown
                     <PresetDropdown
                       key={`filament-${idx}`}
                       key={`filament-${idx}`}
@@ -763,20 +726,6 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
             </>
             </>
           )}
           )}
 
 
-          {printerMismatch && (
-            <div
-              className="text-sm text-amber-200 bg-amber-900/20 border border-amber-700/40 rounded p-2"
-              role="alert"
-            >
-              {t('slice.printerMismatch', {
-                source: sourcePrinterModel,
-                target: printerProfileName,
-                defaultValue:
-                  'This 3MF was sliced for {{source}}, but you picked {{target}}. The slicer CLI cannot re-slice a 3MF for a different printer — open the source in Bambu Studio, change the printer, and re-export.',
-              })}
-            </div>
-          )}
-
           {errorMessage && (
           {errorMessage && (
             <div className="text-sm text-red-400 bg-red-900/20 border border-red-900/40 rounded p-2" role="alert">
             <div className="text-sm text-red-400 bg-red-900/20 border border-red-900/40 rounded p-2" role="alert">
               {errorMessage}
               {errorMessage}
@@ -792,7 +741,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
             disabled={isEnqueuing}
             disabled={isEnqueuing}
             className="px-3 py-1.5 text-sm rounded-md border border-bambu-dark-tertiary text-bambu-gray hover:text-white hover:border-bambu-gray transition-colors disabled:opacity-50"
             className="px-3 py-1.5 text-sm rounded-md border border-bambu-dark-tertiary text-bambu-gray hover:text-white hover:border-bambu-gray transition-colors disabled:opacity-50"
           >
           >
-            {t('common.cancel', 'Cancel')}
+            {t('common.cancel')}
           </button>
           </button>
           <button
           <button
             type="button"
             type="button"
@@ -806,10 +755,10 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
             {isEnqueuing ? (
             {isEnqueuing ? (
               <>
               <>
                 <Loader2 className="w-4 h-4 animate-spin" />
                 <Loader2 className="w-4 h-4 animate-spin" />
-                {t('slice.enqueuing', 'Submitting slice job…')}
+                {t('slice.enqueuing')}
               </>
               </>
             ) : (
             ) : (
-              t('slice.action', 'Slice')
+              t('slice.action')
             )}
             )}
           </button>
           </button>
         </div>
         </div>
@@ -885,7 +834,7 @@ function BedTypeDropdown({
   return (
   return (
     <label className="block">
     <label className="block">
       <span className="block text-xs text-bambu-gray mb-1">
       <span className="block text-xs text-bambu-gray mb-1">
-        {t('slice.bedType.label', 'Build plate')}
+        {t('slice.bedType.label')}
       </span>
       </span>
       <select
       <select
         value={value ?? ''}
         value={value ?? ''}
@@ -893,7 +842,7 @@ function BedTypeDropdown({
         disabled={disabled}
         disabled={disabled}
         className="w-full px-3 py-2 rounded-md bg-bambu-dark border border-bambu-dark-tertiary text-white text-sm focus:outline-none focus:border-bambu-gray disabled:opacity-50"
         className="w-full px-3 py-2 rounded-md bg-bambu-dark border border-bambu-dark-tertiary text-white text-sm focus:outline-none focus:border-bambu-gray disabled:opacity-50"
       >
       >
-        <option value="">{t('slice.bedType.auto', 'Auto (use process preset)')}</option>
+        <option value="">{t('slice.bedType.auto')}</option>
         {BED_TYPE_OPTIONS.map((opt) => (
         {BED_TYPE_OPTIONS.map((opt) => (
           <option key={opt.value} value={opt.value}>
           <option key={opt.value} value={opt.value}>
             {t(opt.labelKey, opt.fallback)}
             {t(opt.labelKey, opt.fallback)}
@@ -959,8 +908,8 @@ function PresetDropdown({ label, slot, data, value, onChange, disabled, swatchCo
       >
       >
         <option value="">
         <option value="">
           {totalEntries === 0
           {totalEntries === 0
-            ? t('slice.noPresetsForSlot', 'No presets available')
-            : t('slice.selectPreset', '— Select a preset —')}
+            ? t('slice.noPresetsForSlot')
+            : t('slice.selectPreset')}
         </option>
         </option>
         {sections.map((section) => (
         {sections.map((section) => (
           <optgroup key={section.tierLabel} label={section.tierLabel}>
           <optgroup key={section.tierLabel} label={section.tierLabel}>
@@ -992,7 +941,7 @@ function BundlePicker({ bundles, selectedId, onChange, disabled }: BundlePickerP
     <label className="block">
     <label className="block">
       <span className="block text-sm text-bambu-gray mb-1 inline-flex items-center gap-1.5">
       <span className="block text-sm text-bambu-gray mb-1 inline-flex items-center gap-1.5">
         <Package className="w-3.5 h-3.5" />
         <Package className="w-3.5 h-3.5" />
-        {t('slice.bundle', 'Slicer bundle')}
+        {t('slice.bundle')}
       </span>
       </span>
       <select
       <select
         value={selectedId ?? ''}
         value={selectedId ?? ''}
@@ -1001,7 +950,7 @@ function BundlePicker({ bundles, selectedId, onChange, disabled }: BundlePickerP
         className="w-full px-3 py-2 rounded-md bg-bambu-dark border border-bambu-dark-tertiary text-white text-sm focus:outline-none focus:border-bambu-gray disabled:opacity-50"
         className="w-full px-3 py-2 rounded-md bg-bambu-dark border border-bambu-dark-tertiary text-white text-sm focus:outline-none focus:border-bambu-gray disabled:opacity-50"
       >
       >
         <option value="">
         <option value="">
-          {t('slice.bundleNone', '— None (pick presets individually) —')}
+          {t('slice.bundleNone')}
         </option>
         </option>
         {bundles.map((b) => (
         {bundles.map((b) => (
           <option key={b.id} value={b.id}>
           <option key={b.id} value={b.id}>
@@ -1056,8 +1005,8 @@ function BundleStringDropdown({
       >
       >
         <option value="">
         <option value="">
           {options.length === 0
           {options.length === 0
-            ? t('slice.noPresetsForSlot', 'No presets available')
-            : t('slice.selectPreset', '— Select a preset —')}
+            ? t('slice.noPresetsForSlot')
+            : t('slice.selectPreset')}
         </option>
         </option>
         {options.map((name) => (
         {options.map((name) => (
           <option key={name} value={name}>
           <option key={name} value={name}>

+ 3 - 1
frontend/src/i18n/locales/de.ts

@@ -3442,10 +3442,12 @@ export default {
     previewToast: '{{name}} wird analysiert — {{elapsed}}',
     previewToast: '{{name}} wird analysiert — {{elapsed}}',
     previewWithProgress: '{{name}} wird analysiert — {{stage}} ({{percent}}%) — {{elapsed}}',
     previewWithProgress: '{{name}} wird analysiert — {{stage}} ({{percent}}%) — {{elapsed}}',
     notUsedByPlate: '— wird von dieser Platte nicht verwendet',
     notUsedByPlate: '— wird von dieser Platte nicht verwendet',
-    printerMismatch: 'Dieses 3MF wurde für {{source}} gesliced, du hast aber {{target}} ausgewählt. Der Slicer-CLI kann ein 3MF nicht für einen anderen Drucker neu slicen — öffne die Quelle in Bambu Studio, ändere den Drucker und exportiere neu.',
     noPresetsForSlot: 'Keine Profile verfügbar',
     noPresetsForSlot: 'Keine Profile verfügbar',
     presetsLoadFailed: 'Profile konnten nicht geladen werden. Importiere sie zuerst unter Einstellungen → Profile.',
     presetsLoadFailed: 'Profile konnten nicht geladen werden. Importiere sie zuerst unter Einstellungen → Profile.',
     allPresetsRequired: 'Alle Profile müssen ausgewählt sein',
     allPresetsRequired: 'Alle Profile müssen ausgewählt sein',
+    bundle: 'Slicer-Bundle',
+    bundleNone: '— Keines (Profile einzeln auswählen) —',
+    bundleAllRequired: 'Bundle-Prozess und jeder Filament-Slot müssen ausgewählt sein',
     enqueuing: 'Slice-Auftrag wird übermittelt…',
     enqueuing: 'Slice-Auftrag wird übermittelt…',
     queued: 'In Warteschlange…',
     queued: 'In Warteschlange…',
     failed: 'Slicen fehlgeschlagen. Logs des Slicer-Sidecars prüfen.',
     failed: 'Slicen fehlgeschlagen. Logs des Slicer-Sidecars prüfen.',

+ 3 - 1
frontend/src/i18n/locales/en.ts

@@ -3445,10 +3445,12 @@ export default {
     previewToast: 'Analyzing {{name}} — {{elapsed}}',
     previewToast: 'Analyzing {{name}} — {{elapsed}}',
     previewWithProgress: 'Analyzing {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
     previewWithProgress: 'Analyzing {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
     notUsedByPlate: '— not used by this plate',
     notUsedByPlate: '— not used by this plate',
-    printerMismatch: 'This 3MF was sliced for {{source}}, but you picked {{target}}. The slicer CLI cannot re-slice a 3MF for a different printer — open the source in Bambu Studio, change the printer, and re-export.',
     noPresetsForSlot: 'No presets available',
     noPresetsForSlot: 'No presets available',
     presetsLoadFailed: 'Failed to load presets. Open Settings → Profiles to import them first.',
     presetsLoadFailed: 'Failed to load presets. Open Settings → Profiles to import them first.',
     allPresetsRequired: 'All presets must be selected',
     allPresetsRequired: 'All presets must be selected',
+    bundle: 'Slicer bundle',
+    bundleNone: '— None (pick presets individually) —',
+    bundleAllRequired: 'Bundle process and every filament slot must be picked',
     enqueuing: 'Submitting slice job…',
     enqueuing: 'Submitting slice job…',
     queued: 'Queued…',
     queued: 'Queued…',
     failed: 'Slicing failed. Check the slicer sidecar logs.',
     failed: 'Slicing failed. Check the slicer sidecar logs.',

+ 3 - 1
frontend/src/i18n/locales/fr.ts

@@ -3431,10 +3431,12 @@ export default {
     previewToast: 'Analyse de {{name}} – {{elapsed}}',
     previewToast: 'Analyse de {{name}} – {{elapsed}}',
     previewWithProgress: 'Analyse de {{name}} – {{stage}} ({{percent}} %) – {{elapsed}}',
     previewWithProgress: 'Analyse de {{name}} – {{stage}} ({{percent}} %) – {{elapsed}}',
     notUsedByPlate: '— non utilisé par cette plaque',
     notUsedByPlate: '— non utilisé par cette plaque',
-    printerMismatch: 'Ce 3MF a été découpé pour {{source}}, mais vous avez choisi {{target}}. La CLI du slicer ne peut pas re-découper un 3MF pour une autre imprimante — ouvrez la source dans Bambu Studio, changez d\'imprimante et réexportez.',
     noPresetsForSlot: 'Aucun préréglage disponible',
     noPresetsForSlot: 'Aucun préréglage disponible',
     presetsLoadFailed: 'Échec du chargement des préréglages. Ouvrez Paramètres → Profils pour les importer d\'abord.',
     presetsLoadFailed: 'Échec du chargement des préréglages. Ouvrez Paramètres → Profils pour les importer d\'abord.',
     allPresetsRequired: 'Tous les préréglages doivent être sélectionnés',
     allPresetsRequired: 'Tous les préréglages doivent être sélectionnés',
+    bundle: 'Pack de découpage',
+    bundleNone: '— Aucun (choisir les préréglages individuellement) —',
+    bundleAllRequired: 'Le processus du pack et chaque emplacement de filament doivent être choisis',
     enqueuing: 'Envoi du travail de découpage…',
     enqueuing: 'Envoi du travail de découpage…',
     queued: 'En file d\'attente…',
     queued: 'En file d\'attente…',
     failed: 'Échec du découpage. Vérifiez les journaux du sidecar.',
     failed: 'Échec du découpage. Vérifiez les journaux du sidecar.',

+ 3 - 1
frontend/src/i18n/locales/it.ts

@@ -3430,10 +3430,12 @@ export default {
     previewToast: 'Analisi di {{name}} – {{elapsed}}',
     previewToast: 'Analisi di {{name}} – {{elapsed}}',
     previewWithProgress: 'Analisi di {{name}} – {{stage}} ({{percent}}%) – {{elapsed}}',
     previewWithProgress: 'Analisi di {{name}} – {{stage}} ({{percent}}%) – {{elapsed}}',
     notUsedByPlate: '— non usato da questo piano',
     notUsedByPlate: '— non usato da questo piano',
-    printerMismatch: 'Questo 3MF è stato sezionato per {{source}}, ma hai scelto {{target}}. La CLI del slicer non può ri-sezionare un 3MF per una stampante diversa — apri la sorgente in Bambu Studio, cambia stampante e ri-esporta.',
     noPresetsForSlot: 'Nessun preset disponibile',
     noPresetsForSlot: 'Nessun preset disponibile',
     presetsLoadFailed: 'Caricamento preset fallito. Apri Impostazioni → Profili per importarli prima.',
     presetsLoadFailed: 'Caricamento preset fallito. Apri Impostazioni → Profili per importarli prima.',
     allPresetsRequired: 'Tutti i preset devono essere selezionati',
     allPresetsRequired: 'Tutti i preset devono essere selezionati',
+    bundle: 'Bundle slicer',
+    bundleNone: '— Nessuno (scegli i preset singolarmente) —',
+    bundleAllRequired: 'Devi scegliere il processo del bundle e ogni slot filamento',
     enqueuing: 'Invio lavoro di slicing…',
     enqueuing: 'Invio lavoro di slicing…',
     queued: 'In coda…',
     queued: 'In coda…',
     failed: 'Slicing fallito. Controlla i log del sidecar.',
     failed: 'Slicing fallito. Controlla i log del sidecar.',

+ 3 - 1
frontend/src/i18n/locales/ja.ts

@@ -3442,10 +3442,12 @@ export default {
     previewToast: '{{name}}を分析中 – {{elapsed}}',
     previewToast: '{{name}}を分析中 – {{elapsed}}',
     previewWithProgress: '{{name}}を分析中 – {{stage}} ({{percent}}%) – {{elapsed}}',
     previewWithProgress: '{{name}}を分析中 – {{stage}} ({{percent}}%) – {{elapsed}}',
     notUsedByPlate: '— このプレートでは使用しない',
     notUsedByPlate: '— このプレートでは使用しない',
-    printerMismatch: 'この3MFは{{source}}用にスライスされていますが、{{target}}を選択しました。スライサーCLIは異なるプリンター用に3MFを再スライスできません — Bambu Studioでソースを開き、プリンターを変更して再エクスポートしてください。',
     noPresetsForSlot: 'プリセットなし',
     noPresetsForSlot: 'プリセットなし',
     presetsLoadFailed: 'プリセットの読み込みに失敗。先に設定 → プロファイルからインポートしてください。',
     presetsLoadFailed: 'プリセットの読み込みに失敗。先に設定 → プロファイルからインポートしてください。',
     allPresetsRequired: 'すべてのプリセットを選択する必要があります',
     allPresetsRequired: 'すべてのプリセットを選択する必要があります',
+    bundle: 'スライサーバンドル',
+    bundleNone: '— なし(プリセットを個別に選択)—',
+    bundleAllRequired: 'バンドルのプロセスとすべてのフィラメントスロットを選択してください',
     enqueuing: 'スライスジョブを送信中…',
     enqueuing: 'スライスジョブを送信中…',
     queued: '待機中…',
     queued: '待機中…',
     failed: 'スライスに失敗。サイドカーのログを確認してください。',
     failed: 'スライスに失敗。サイドカーのログを確認してください。',

+ 3 - 1
frontend/src/i18n/locales/pt-BR.ts

@@ -3430,10 +3430,12 @@ export default {
     previewToast: 'Analisando {{name}} – {{elapsed}}',
     previewToast: 'Analisando {{name}} – {{elapsed}}',
     previewWithProgress: 'Analisando {{name}} – {{stage}} ({{percent}}%) – {{elapsed}}',
     previewWithProgress: 'Analisando {{name}} – {{stage}} ({{percent}}%) – {{elapsed}}',
     notUsedByPlate: '— não usado por esta mesa',
     notUsedByPlate: '— não usado por esta mesa',
-    printerMismatch: 'Este 3MF foi fatiado para {{source}}, mas você escolheu {{target}}. A CLI do fatiador não pode re-fatiar um 3MF para outra impressora — abra a origem no Bambu Studio, troque a impressora e re-exporte.',
     noPresetsForSlot: 'Nenhuma predefinição disponível',
     noPresetsForSlot: 'Nenhuma predefinição disponível',
     presetsLoadFailed: 'Falha ao carregar predefinições. Abra Configurações → Perfis para importá-las primeiro.',
     presetsLoadFailed: 'Falha ao carregar predefinições. Abra Configurações → Perfis para importá-las primeiro.',
     allPresetsRequired: 'Todas as predefinições devem ser selecionadas',
     allPresetsRequired: 'Todas as predefinições devem ser selecionadas',
+    bundle: 'Pacote do fatiador',
+    bundleNone: '— Nenhum (escolher predefinições individualmente) —',
+    bundleAllRequired: 'O processo do pacote e cada slot de filamento devem ser escolhidos',
     enqueuing: 'Enviando trabalho de fatiamento…',
     enqueuing: 'Enviando trabalho de fatiamento…',
     queued: 'Na fila…',
     queued: 'Na fila…',
     failed: 'Falha ao fatiar. Verifique os logs do sidecar.',
     failed: 'Falha ao fatiar. Verifique os logs do sidecar.',

+ 3 - 1
frontend/src/i18n/locales/zh-CN.ts

@@ -3430,10 +3430,12 @@ export default {
     previewToast: '分析 {{name}} — {{elapsed}}',
     previewToast: '分析 {{name}} — {{elapsed}}',
     previewWithProgress: '分析 {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
     previewWithProgress: '分析 {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
     notUsedByPlate: '— 此打印板未使用',
     notUsedByPlate: '— 此打印板未使用',
-    printerMismatch: '此 3MF 是为 {{source}} 切片的,但您选择了 {{target}}。切片器 CLI 无法为不同的打印机重新切片 3MF — 请在 Bambu Studio 中打开源文件,更改打印机并重新导出。',
     noPresetsForSlot: '无可用预设',
     noPresetsForSlot: '无可用预设',
     presetsLoadFailed: '加载预设失败。请先打开设置 → 配置文件以导入。',
     presetsLoadFailed: '加载预设失败。请先打开设置 → 配置文件以导入。',
     allPresetsRequired: '必须选择所有预设',
     allPresetsRequired: '必须选择所有预设',
+    bundle: '切片器套装',
+    bundleNone: '— 无(单独选择预设)—',
+    bundleAllRequired: '必须选择套装的工艺和每个耗材槽',
     enqueuing: '提交切片任务中…',
     enqueuing: '提交切片任务中…',
     queued: '已排队…',
     queued: '已排队…',
     failed: '切片失败。请检查切片器 sidecar 日志。',
     failed: '切片失败。请检查切片器 sidecar 日志。',

+ 3 - 1
frontend/src/i18n/locales/zh-TW.ts

@@ -3430,10 +3430,12 @@ export default {
     previewToast: '分析 {{name}} — {{elapsed}}',
     previewToast: '分析 {{name}} — {{elapsed}}',
     previewWithProgress: '分析 {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
     previewWithProgress: '分析 {{name}} — {{stage}} ({{percent}}%) — {{elapsed}}',
     notUsedByPlate: '— 此列印板未使用',
     notUsedByPlate: '— 此列印板未使用',
-    printerMismatch: '此 3MF 是為 {{source}} 切片的,但您選擇了 {{target}}。切片器 CLI 無法為不同的印表機重新切片 3MF — 請在 Bambu Studio 中開啟原始檔案,變更印表機並重新匯出。',
     noPresetsForSlot: '無可用預設',
     noPresetsForSlot: '無可用預設',
     presetsLoadFailed: '載入預設失敗。請先開啟設定 → 設定檔以匯入。',
     presetsLoadFailed: '載入預設失敗。請先開啟設定 → 設定檔以匯入。',
     allPresetsRequired: '必須選擇所有預設',
     allPresetsRequired: '必須選擇所有預設',
+    bundle: '切片器套裝',
+    bundleNone: '— 無(單獨選擇預設)—',
+    bundleAllRequired: '必須選擇套裝的製程和每個耗材槽',
     enqueuing: '提交切片任務中…',
     enqueuing: '提交切片任務中…',
     queued: '已排隊…',
     queued: '已排隊…',
     failed: '切片失敗。請檢查切片器 sidecar 日誌。',
     failed: '切片失敗。請檢查切片器 sidecar 日誌。',

+ 0 - 6
frontend/src/types/plates.ts

@@ -33,11 +33,6 @@ export interface ArchivePlatesResponse {
   plates: PlateMetadata[];
   plates: PlateMetadata[];
   is_multi_plate: boolean;
   is_multi_plate: boolean;
   has_gcode?: boolean;
   has_gcode?: boolean;
-  // Bound printer model from the source 3MF's project_settings.config (e.g.
-  // "Bambu Lab A1"). Used by the SliceModal to warn before slicing if the
-  // user picks a profile for a different printer — the slicer CLI can't
-  // convert a 3MF across printer models.
-  source_printer_model?: string | null;
 }
 }
 
 
 export interface LibraryFilePlatesResponse {
 export interface LibraryFilePlatesResponse {
@@ -45,7 +40,6 @@ export interface LibraryFilePlatesResponse {
   filename: string;
   filename: string;
   plates: PlateMetadata[];
   plates: PlateMetadata[];
   is_multi_plate: boolean;
   is_multi_plate: boolean;
-  source_printer_model?: string | null;
 }
 }
 
 
 export interface ViewerPlateSelectionState {
 export interface ViewerPlateSelectionState {

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-CHKJFwmW.js


+ 1 - 1
static/index.html

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

Some files were not shown because too many files changed in this diff