Переглянути джерело

Slice with a printer preset saved under a name of its own (#3250)

maziggy 5 днів тому
батько
коміт
3acc52d938
64 змінених файлів з 187 додано та 13 видалено
  1. 3 0
      CHANGELOG.md
  2. 13 0
      backend/app/api/routes/slicer_presets.py
  3. 7 0
      backend/app/schemas/slicer_presets.py
  4. 65 0
      backend/tests/unit/test_slicer_presets.py
  5. 45 0
      frontend/src/__tests__/utils/slicerPrinterMatch.test.ts
  6. 3 0
      frontend/src/api/client.ts
  7. 26 4
      frontend/src/components/SliceModal.tsx
  8. 22 4
      frontend/src/utils/slicerPrinterMatch.ts
  9. 0 0
      static/assets/AdditionalSection-DCNgoguc.js
  10. 0 0
      static/assets/AmsUnitCard-DiIO65tf.js
  11. 0 0
      static/assets/ArchivesPage-BIcFRnMQ.js
  12. 0 0
      static/assets/AssignToAmsModal-ijVouKX5.js
  13. 0 0
      static/assets/CamWallPage-C8Spdzpg.js
  14. 0 0
      static/assets/CameraDiagnoseModal-iwT_sa2N.js
  15. 0 0
      static/assets/CameraPage-DFYuKFlZ.js
  16. 0 0
      static/assets/CameraWall-4K2aLml3.js
  17. 0 0
      static/assets/ColumnConfigModal-B_HCrsvn.js
  18. 0 0
      static/assets/ConfigureAmsSlotModal-BpMwD5Ju.js
  19. 0 0
      static/assets/ConnectAuthorizePage-yLuVktAI.js
  20. 0 0
      static/assets/ContextMenu-B0CaxRaq.js
  21. 0 0
      static/assets/ExternalLinkPage-BLbp48_E.js
  22. 0 1
      static/assets/FileManagerPage-C5hOQBhH.js
  23. 0 0
      static/assets/FileUploadModal-4KA5H9k9.js
  24. 0 0
      static/assets/FinancePage-B40s4ova.js
  25. 0 0
      static/assets/GCodeViewerPage-1vGQvSx6.js
  26. 0 0
      static/assets/GroupEditPage-DzA0LMuE.js
  27. 0 0
      static/assets/ImagePreviewModal-P2RnwWrB.js
  28. 0 0
      static/assets/InventoryPage-ChYOapWj.js
  29. 0 0
      static/assets/LibraryTrashPage-CE-NbaoZ.js
  30. 0 0
      static/assets/LoginPage-BBjWNJQ3.js
  31. 0 0
      static/assets/MaintenancePage-BmhPY8nt.js
  32. 0 0
      static/assets/ModelSourcesPage-8hwyyd56.js
  33. 0 0
      static/assets/NotificationsPage-B99gzNZY.js
  34. 0 1
      static/assets/PdfPreviewModal-B8GZtc2S.js
  35. 0 0
      static/assets/PhotoGalleryModal-coLmS2zk.js
  36. 0 0
      static/assets/PrinterLocationsPage-BTK2oaVo.js
  37. 0 0
      static/assets/PrintersPage-D2ZdkE_n.js
  38. 0 0
      static/assets/ProfilesPage-CZK1Wjv0.js
  39. 0 0
      static/assets/ProjectDetailPage-BqzAj5SO.js
  40. 0 0
      static/assets/ProjectsPage-DV8S24m4.js
  41. 0 0
      static/assets/QueuePage-CLqxzVSt.js
  42. 0 0
      static/assets/RichTextEditor-BIVUGeuc.js
  43. 0 0
      static/assets/SettingsPage-DG0TdEz4.js
  44. 0 0
      static/assets/SetupPage-DeH1XCpP.js
  45. 0 0
      static/assets/SliceModal-BrHiVAZR.js
  46. 0 0
      static/assets/SliceModal-gNieU_I9.js
  47. 0 0
      static/assets/SpoolBuddyAmsPage-BxC7e1Ar.js
  48. 0 0
      static/assets/SpoolBuddyCalibrationPage-DGmt2gR9.js
  49. 0 0
      static/assets/SpoolBuddyDashboard-15BggDPk.js
  50. 0 0
      static/assets/SpoolBuddyInventoryPage-CsZBLmRc.js
  51. 1 1
      static/assets/SpoolBuddySettingsPage-BnqEtm1v.js
  52. 0 0
      static/assets/SpoolBuddyWriteTagPage-GYEyc_Z9.js
  53. 0 0
      static/assets/SpreadsheetPreviewModal-CKID_GqJ.js
  54. 0 0
      static/assets/StatsPage-B1cIgUwl.js
  55. 0 0
      static/assets/StreamOverlayPage-BIJmUTGS.js
  56. 0 0
      static/assets/SystemInfoPage-pFnK7PEF.js
  57. 0 0
      static/assets/index-CxFwVtB_.js
  58. 1 1
      static/assets/inventorySearch-X-uWpJqR.js
  59. 0 0
      static/assets/locationSensorDefaults-Cv_64Q1r.js
  60. 0 0
      static/assets/pdf-CbRqzJ0U.js
  61. 0 0
      static/assets/slicerPrinterMatch-CUdoNag2.js
  62. 0 0
      static/assets/slicerPrinterMatch-g7Dr6p0c.js
  63. 0 0
      static/assets/utils-DMkcDNHB.js
  64. 1 1
      static/index.html

+ 3 - 0
CHANGELOG.md

@@ -149,6 +149,9 @@ All notable changes to Bambuddy will be documented in this file.
 - **The frontend build no longer warns about `path` and `crypto` being externalized for the STEP previewer (#2976)** — `occt-import-js`, the Emscripten build behind STEP previews, requires both modules, but only inside its `ENVIRONMENT_IS_NODE` branches; in the browser it loads its `.wasm` from the URL the preview worker passes and draws randomness from `crypto.getRandomValues`. Vite still externalized both and printed two warnings on every build. `vite.config.ts` now drops exactly those two warnings for that one package through `build.rolldownOptions.onLog`, so an externalization anywhere else, or of any other module, still shows.
 
 ### Fixed
+- **Slicing failed with a printer preset saved under a name of its own (#3250, reported by @bobobo1618)** — A printer preset copied from a stock one and renamed, such as "Bambu Lab H2D 0.4 nozzle - Apply Skew Calibration", failed every slice with "The selected printer is not compatible with the process preset in the 3mf", even with a stock process the stock printer slices fine.
+  - **Why:** the slicer checks a process against the stock preset a printer preset was saved from. The slicer sidecar handed it the renamed preset as a stock preset in its own right, and no process lists that name. It now names the stock preset the printer was saved from, while still slicing with the renamed preset's own settings, start G-code included. A process or filament preset that lists the renamed printer itself keeps fitting it, as it does in the slicer. Needs the updated slicer sidecar image (OrcaSlicer and Bambu Studio).
+  - **The Slice dialog** read the printer model out of the preset's name, which a name of your own may not contain, so it misjudged which process and filament presets fit. It now also checks against the stock preset the printer was saved from, for imported and OrcaSlicer Cloud printer presets.
 - **A print started from the printer's screen took about six seconds longer to archive (#3009, reported by @bdwilson)** — A job started on the printer's own screen reports its file's full name, extension included (`Part.3mf`), where a job Bambuddy sends reports the bare name. Bambuddy added the extensions to it anyway and asked the printer for `Part.3mf.gcode.3mf` and `Part.3mf.3mf` first, six connections that could not succeed, before trying the real file. The check that reattaches a running print after a Bambuddy restart compared against the same doubled name, so it could not find such a print. When the printer reports the same full name as both the job and its file, that name is now taken as the file itself: it is the only name tried, and the restart check also matches that exact file. Prints Bambuddy dispatches are looked up as before.
 - **A queue job stayed "printing" for five minutes when its file name ended in a space (#3241, reported by @Thomansky)** — A file saved as `Part .gcode.3mf` is sent to the printer as `Part `, and the printer reports it back as `Part_`, turning that space into an underscore like every other. Bambuddy dropped the space from its own side before comparing, so the names differed, the finish was ignored as belonging to another print, and the job showed "printing" until the stale-job cleanup closed it about five minutes later, holding up the printer's next job. The finish photo's plate restore skipped these prints for the same reason. A space at the start of the name had the same effect. Both are now matched.
 - **The finish photo never raised the plate back into view (#3240, reported by @Thomansky)** — Since 1.2.5.2, Bambuddy is meant to raise the build plate back to just above the finished print before the finish photo, then lower it again, because the printer's end G-code drops the plate about 100 mm. It never did, on any printer: the lookup for the print's height failed every time and logged nothing above debug level. It read the archived file from a setting that does not exist, and it matched the archive by exact name, while the printer reports a name with its spaces turned into underscores. Both are fixed, so the plate now really moves after a print when **Restore plate for finish photo** is on, which it is by default. Turn it off under **Settings** > **General** to keep the photo where the plate stops. Because the plate now really moves, the print's height is taken only from the archive Bambuddy linked to that print when it started, and from the plate that was sent, never from another archive that merely has the same name: every plate of a multi-plate file shares one name, and support bundles showed the old lookup picking another plate's archive, stopped only by its layer count. That archive must also match the print's name exactly, never part of it or a shortened version; its layer count must agree with the printer's; and the plate stays put when another job is queued for the printer or the print was not linked to an archive.

+ 13 - 0
backend/app/api/routes/slicer_presets.py

@@ -303,6 +303,8 @@ async def _fetch_orca_cloud_presets(
                 # which the picker treats as usable and auto-picks for a
                 # printer the profile was never built for.
                 preset.compatible_printers = _content_compatible_printers(content)
+            elif slot == "printer":
+                preset.inherits = _content_inherits(content)
             slots[slot].append(preset)
         _orca_cloud_cache[cache_key] = (now, slots)
         return slots, "ok"
@@ -329,10 +331,21 @@ async def _fetch_local_presets(db: AsyncSession) -> dict[str, list[UnifiedPreset
             # process / filament dropdowns by the selected printer without
             # falling back to the @BBL name matcher.
             preset.compatible_printers = _parse_compatible_printers(p.compatible_printers)
+        elif slot == "printer":
+            # The preset it was saved from (#3250); see UnifiedPreset.inherits.
+            preset.inherits = (p.inherits or "").strip() or None
         slots[slot].append(preset)
     return slots
 
 
+def _content_inherits(content: dict) -> str | None:
+    """The preset a profile was saved from, out of its content dict (#3250)."""
+    raw = content.get("inherits") if isinstance(content, dict) else None
+    if not isinstance(raw, str):
+        return None
+    return raw.strip() or None
+
+
 def _content_compatible_printers(content: dict) -> list[str] | None:
     """Pull ``compatible_printers`` out of a profile content dict.
 

+ 7 - 0
backend/app/schemas/slicer_presets.py

@@ -40,6 +40,12 @@ class UnifiedPreset(BaseModel):
     process / filament dropdowns by the selected printer (#1325); when it is
     ``None`` the modal falls back to matching the preset name against the
     ``@BBL <code>`` printer-model registry.
+
+    ``inherits`` is the preset a printer preset was saved from, for the local
+    and OrcaSlicer Cloud tiers. A printer preset saved under a name of its own
+    ("Bambu Lab H2D 0.4 nozzle - Skew") is listed in no process's
+    ``compatible_printers`` and its name says nothing reliable about the
+    model, so the SliceModal matches against this parent as well (#3250).
     """
 
     id: str
@@ -48,6 +54,7 @@ class UnifiedPreset(BaseModel):
     filament_type: str | None = None
     filament_colour: str | None = None
     compatible_printers: list[str] | None = None
+    inherits: str | None = None
 
 
 class UnifiedPresetsBySlot(BaseModel):

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

@@ -395,6 +395,34 @@ class TestFetchOrcaCloudPresets:
         # Printer presets have nothing to be compatible with.
         assert slots["printer"][0].compatible_printers is None
 
+    @pytest.mark.asyncio
+    async def test_printer_preset_carries_the_preset_it_was_saved_from(self):
+        """#3250: a printer preset saved under a name of its own is in no
+        process's compatible_printers; the SliceModal needs its parent."""
+        sp._orca_cloud_cache.clear()
+        svc_mock = MagicMock()
+        svc_mock.list_profiles = AsyncMock(
+            return_value=[
+                {
+                    "id": "m1",
+                    "name": "Bambu Lab H2D 0.4 nozzle - Skew",
+                    "content": {"type": "printer", "inherits": " Bambu Lab H2D 0.4 nozzle "},
+                },
+                {"id": "m2", "name": "Orca X1C", "content": {"type": "printer", "inherits": ""}},
+                {"id": "m3", "name": "Odd", "content": {"type": "printer", "inherits": ["x"]}},
+            ]
+        )
+        svc_mock.close = AsyncMock()
+        user = MagicMock(id=1)
+        user.has_permission = MagicMock(return_value=True)
+        with (
+            patch.object(sp, "_load_orca_credentials", AsyncMock(return_value=self._orca_creds("tok"))),
+            patch.object(sp, "_build_orca_service", AsyncMock(return_value=svc_mock)),
+        ):
+            slots, _ = await sp._fetch_orca_cloud_presets(MagicMock(), user)
+
+        assert [p.inherits for p in slots["printer"]] == ["Bambu Lab H2D 0.4 nozzle", None, None]
+
     @pytest.mark.asyncio
     async def test_missing_or_malformed_compatible_printers_stays_none(self):
         """No data must read as "unknown", never as "compatible with nothing" —
@@ -934,3 +962,40 @@ class TestNoUserWithAuthOn:
         assert status == "not_authenticated"
         assert slots == {"printer": [], "process": [], "filament": []}
         load.assert_not_called()
+
+
+class TestLocalPrinterInherits:
+    """#3250: local printer presets report the preset they were saved from."""
+
+    @pytest.mark.asyncio
+    async def test_local_printer_preset_carries_inherits(self, db_session):
+        from backend.app.models.local_preset import LocalPreset
+
+        db_session.add_all(
+            [
+                LocalPreset(
+                    name="Bambu Lab H2D 0.4 nozzle - Skew",
+                    preset_type="printer",
+                    source="orcaslicer",
+                    setting="{}",
+                    inherits="Bambu Lab H2D 0.4 nozzle",
+                ),
+                LocalPreset(name="Plain", preset_type="printer", source="orcaslicer", setting="{}", inherits="  "),
+                LocalPreset(
+                    name="0.20mm Mine",
+                    preset_type="process",
+                    source="orcaslicer",
+                    setting="{}",
+                    inherits="0.20mm Standard @BBL H2D",
+                ),
+            ]
+        )
+        await db_session.commit()
+
+        slots = await sp._fetch_local_presets(db_session)
+
+        by_name = {p.name: p for p in slots["printer"]}
+        assert by_name["Bambu Lab H2D 0.4 nozzle - Skew"].inherits == "Bambu Lab H2D 0.4 nozzle"
+        assert by_name["Plain"].inherits is None
+        # Only printer presets need it; a process is matched by its own list.
+        assert slots["process"][0].inherits is None

+ 45 - 0
frontend/src/__tests__/utils/slicerPrinterMatch.test.ts

@@ -587,3 +587,48 @@ describe("presetCompatibility — BambuStudio's \"# \" user-clone prefix", () =>
     ).toBe('unknown');
   });
 });
+
+describe('presetCompatibility — printer preset saved under a name of its own (#3250)', () => {
+  const RENAMED = 'Bambu Lab H2D 0.4 nozzle - Apply Skew Calibration - Only use once';
+  const H2D = 'Bambu Lab H2D 0.4 nozzle';
+  const index = buildCompatibilityIndex(PRINTER_MODELS, { [RENAMED]: H2D });
+
+  it('matches a stock process by name through the parent', () => {
+    expect(presetCompatibility({ name: '0.16mm Balanced Quality @BBL H2D' }, 'process', RENAMED, index)).toBe('match');
+  });
+
+  it('still rules out a process for another printer', () => {
+    expect(presetCompatibility({ name: '0.20mm Standard @BBL X1C' }, 'process', RENAMED, index)).toBe('mismatch');
+  });
+
+  it('matches a list naming the parent', () => {
+    expect(
+      presetCompatibility({ name: 'Mine', compatible_printers: [H2D] }, 'process', RENAMED, index),
+    ).toBe('match');
+  });
+
+  it('matches a list naming the renamed preset itself', () => {
+    expect(
+      presetCompatibility({ name: 'Mine', compatible_printers: [RENAMED] }, 'process', RENAMED, index),
+    ).toBe('match');
+  });
+
+  it('rejects a list naming neither', () => {
+    expect(
+      presetCompatibility({ name: 'Mine', compatible_printers: [X1C] }, 'process', RENAMED, index),
+    ).toBe('mismatch');
+  });
+
+  it('without a known parent the name alone decides, as before', () => {
+    const noParents = buildCompatibilityIndex(PRINTER_MODELS);
+    expect(
+      presetCompatibility({ name: '0.16mm Balanced Quality @BBL H2D' }, 'process', RENAMED, noParents),
+    ).toBe('mismatch');
+  });
+
+  it('ignores names that collide with Object.prototype', () => {
+    expect(
+      presetCompatibility({ name: '0.16mm Balanced Quality @BBL H2D' }, 'process', 'constructor', index),
+    ).toBe('unknown');
+  });
+});

+ 3 - 0
frontend/src/api/client.ts

@@ -1993,6 +1993,9 @@ export interface UnifiedPreset {
   // the process / filament dropdowns by the selected printer using this when
   // present (#1325).
   compatible_printers?: string[] | null;
+  // Printer presets only: the preset it was saved from, for the local and
+  // OrcaSlicer Cloud tiers (#3250).
+  inherits?: string | null;
 }
 export interface UnifiedPresetsBySlot {
   printer: UnifiedPreset[];

+ 26 - 4
frontend/src/components/SliceModal.tsx

@@ -486,9 +486,22 @@ export function SliceModal({ source, onClose, defaultAutoArrange = false }: Slic
   // Compatibility ground truth: the slicer's own `compatible_printers` list
   // on local-imported presets, plus the @BBL <code> name fallback for cloud
   // / standard presets via the backend Bambu printer-model registry.
+  // Plus, for printer presets saved under a name of their own, the preset
+  // each was saved from (#3250).
+  const printerParents = useMemo<Record<string, string>>(() => {
+    const out: Record<string, string> = {};
+    const data = presetsQuery.data;
+    if (!data) return out;
+    for (const tier of [data.local, data.orca_cloud, data.cloud, data.standard]) {
+      for (const p of tier.printer) {
+        if (p.inherits && !Object.hasOwn(out, p.name)) out[p.name] = p.inherits;
+      }
+    }
+    return out;
+  }, [presetsQuery.data]);
   const compatIndex = useMemo<PrinterCompatibilityIndex>(
-    () => buildCompatibilityIndex(printerModelsQuery.data ?? {}),
-    [printerModelsQuery.data],
+    () => buildCompatibilityIndex(printerModelsQuery.data ?? {}, printerParents),
+    [printerModelsQuery.data, printerParents],
   );
 
   // What the connected printers have loaded (#3172), for the two filters and
@@ -510,8 +523,17 @@ export function SliceModal({ source, onClose, defaultAutoArrange = false }: Slic
   // AMS says nothing about what an X1C job can start on. A profile whose
   // model can't be read keeps every connected printer.
   const spoolPrinters = useMemo(
-    () => printersOfModel(loadedPrinters, printerPresetModel(selectedPrinterName, printerModels)),
-    [loadedPrinters, selectedPrinterName, printerModels],
+    // The parent names the model reliably; the user's own name may not.
+    () => printersOfModel(
+      loadedPrinters,
+      printerPresetModel(
+        (selectedPrinterName && Object.hasOwn(printerParents, selectedPrinterName)
+          ? printerParents[selectedPrinterName]
+          : null) || selectedPrinterName,
+        printerModels,
+      ),
+    ),
+    [loadedPrinters, selectedPrinterName, printerModels, printerParents],
   );
   const filamentNameIndex = useMemo(
     () => (presetsQuery.data ? buildFilamentNameIndex(presetsQuery.data) : new Map()),

+ 22 - 4
frontend/src/utils/slicerPrinterMatch.ts

@@ -29,6 +29,11 @@ export type PrinterCompatibility = 'match' | 'mismatch' | 'unknown';
 // (raw-token comparison), and gracefully degrades otherwise.
 export interface PrinterCompatibilityIndex {
   bambuModelByShortCode: Record<string, string>;
+  // Printer preset name → the preset it was saved from (#3250). A printer
+  // preset saved under a name of its own ("Bambu Lab H2D 0.4 nozzle - Skew")
+  // is in no process's compatible_printers and its name may not parse as a
+  // model; its parent is what the slicer checks against.
+  printerParents?: Record<string, string>;
 }
 
 /** An empty index — used when the model map hasn't loaded yet. */
@@ -99,9 +104,11 @@ function buildShortCodeMap(
  */
 export function buildCompatibilityIndex(
   printerModels: Record<string, string> = {},
+  printerParents: Record<string, string> = {},
 ): PrinterCompatibilityIndex {
   return {
     bambuModelByShortCode: buildShortCodeMap(printerModels),
+    printerParents,
   };
 }
 
@@ -310,6 +317,12 @@ export function presetCompatibility(
   index: PrinterCompatibilityIndex,
 ): PrinterCompatibility {
   if (!selectedPrinterName) return 'unknown';
+  // The preset the selected printer was saved from, when it has another name
+  // (#3250). The slicer checks processes against that parent, so either name
+  // counts as the selected printer.
+  const parents = index.printerParents;
+  const parent = parents && Object.hasOwn(parents, selectedPrinterName) ? parents[selectedPrinterName] : null;
+  const parentName = typeof parent === 'string' && parent && parent !== selectedPrinterName ? parent : null;
   // (1) Imported presets carry the slicer's own compatible_printers list —
   // authoritative when set.
   const compat = preset.compatible_printers;
@@ -318,12 +331,17 @@ export function presetCompatibility(
     // system printer lists the *unprefixed* name, and comparing that raw
     // against a selected "# Bambu Lab …" reads as a mismatch — which now
     // hides the preset rather than merely demoting it.
-    const selected = stripUserClonePrefix(selectedPrinterName);
-    return compat.some((name) => stripUserClonePrefix(name) === selected) ? 'match' : 'mismatch';
+    const names = [selectedPrinterName, parentName].filter((n): n is string => !!n).map(stripUserClonePrefix);
+    return compat.some((name) => names.includes(stripUserClonePrefix(name))) ? 'match' : 'mismatch';
   }
   // (2) BambuStudio's `@BBL <model>` name convention — covers cloud /
-  // standard presets that don't carry compatible_printers.
-  return classifyByBambuName(preset.name, selectedPrinterName, index.bambuModelByShortCode);
+  // standard presets that don't carry compatible_printers. A name of the
+  // user's own may not parse as a model at all, so the parent decides
+  // whenever it can.
+  const own = classifyByBambuName(preset.name, selectedPrinterName, index.bambuModelByShortCode);
+  if (own === 'match' || !parentName) return own;
+  const inherited = classifyByBambuName(preset.name, parentName, index.bambuModelByShortCode);
+  return inherited === 'unknown' ? own : inherited;
 }
 
 // model token compiles to a flexible-whitespace word-boundary regex.

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/AdditionalSection-DCNgoguc.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/AmsUnitCard-DiIO65tf.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ArchivesPage-BIcFRnMQ.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/AssignToAmsModal-ijVouKX5.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/CamWallPage-C8Spdzpg.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/CameraDiagnoseModal-iwT_sa2N.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/CameraPage-DFYuKFlZ.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/CameraWall-4K2aLml3.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ColumnConfigModal-B_HCrsvn.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ConfigureAmsSlotModal-BpMwD5Ju.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ConnectAuthorizePage-yLuVktAI.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ContextMenu-B0CaxRaq.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ExternalLinkPage-BLbp48_E.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 1
static/assets/FileManagerPage-C5hOQBhH.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/FileUploadModal-4KA5H9k9.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/FinancePage-B40s4ova.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/GCodeViewerPage-1vGQvSx6.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/GroupEditPage-DzA0LMuE.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ImagePreviewModal-P2RnwWrB.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/InventoryPage-ChYOapWj.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/LibraryTrashPage-CE-NbaoZ.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/LoginPage-BBjWNJQ3.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/MaintenancePage-BmhPY8nt.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ModelSourcesPage-8hwyyd56.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/NotificationsPage-B99gzNZY.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 1
static/assets/PdfPreviewModal-B8GZtc2S.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/PhotoGalleryModal-coLmS2zk.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/PrinterLocationsPage-BTK2oaVo.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/PrintersPage-D2ZdkE_n.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ProfilesPage-CZK1Wjv0.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ProjectDetailPage-BqzAj5SO.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/ProjectsPage-DV8S24m4.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/QueuePage-CLqxzVSt.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/RichTextEditor-BIVUGeuc.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SettingsPage-DG0TdEz4.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SetupPage-DeH1XCpP.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SliceModal-BrHiVAZR.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SliceModal-gNieU_I9.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SpoolBuddyAmsPage-BxC7e1Ar.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SpoolBuddyCalibrationPage-DGmt2gR9.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SpoolBuddyDashboard-15BggDPk.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SpoolBuddyInventoryPage-CsZBLmRc.js


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
static/assets/SpoolBuddySettingsPage-BnqEtm1v.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SpoolBuddyWriteTagPage-GYEyc_Z9.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SpreadsheetPreviewModal-CKID_GqJ.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/StatsPage-B1cIgUwl.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/StreamOverlayPage-BIJmUTGS.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/SystemInfoPage-pFnK7PEF.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/index-CxFwVtB_.js


+ 1 - 1
static/assets/inventorySearch-CnZFjESL.js → static/assets/inventorySearch-X-uWpJqR.js

@@ -1 +1 @@
-import{ft as e}from"./index-DQNUzRiJ.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};
+import{ft as e}from"./index-CxFwVtB_.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/locationSensorDefaults-Cv_64Q1r.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/pdf-CbRqzJ0U.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/slicerPrinterMatch-CUdoNag2.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/slicerPrinterMatch-g7Dr6p0c.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
static/assets/utils-DMkcDNHB.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-DQNUzRiJ.js"></script>
+    <script type="module" crossorigin src="/assets/index-CxFwVtB_.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/chunk-aKtaBQYM.js">
     <link rel="modulepreload" crossorigin href="/assets/client-BdOyoZJz.js">
     <link rel="modulepreload" crossorigin href="/assets/QueryClientProvider-Clbyr4Jg.js">

Деякі файли не було показано, через те що забагато файлів було змінено