feat(slicer): filter process & filament profiles by selected printer (issue #1325)

The Slice dialog listed every process / filament preset regardless of
  the chosen printer (#1325). Picking a printer profile now filters both
  dropdowns to compatible presets; presets resolving to a different Bambu
  model move into a trailing "Other printers" group.

  Matching uses the slicer's own compatible_printers list for imported
  (local) presets, and falls back to the "@BBL <model>" name suffix for
  cloud / standard presets where no compatibility metadata is available,
  so all three tiers are covered. Compatibility-unknown presets (custom
  or untagged) are never hidden. The pre-pick and printer-switch paths
  follow the same rule.

  - UnifiedPreset gains compatible_printers, exposed for the local tier
  - new frontend util slicerPrinterMatch.ts with the matching logic
  - slice.otherPrinters added across all 9 locales
This commit is contained in:
maziggy
2026-05-21 13:33:13 +02:00
parent a51d59eabf
commit 7eba29624b
19 changed files with 909 additions and 511 deletions
+25 -5
View File
@@ -172,15 +172,35 @@ async def _fetch_local_presets(db: AsyncSession) -> dict[str, list[UnifiedPreset
slot = type_to_slot.get(p.preset_type)
if slot is None:
continue
extra: dict[str, str | None] = {}
preset = UnifiedPreset(id=str(p.id), name=p.name, source="local")
if slot == "filament":
extra["filament_type"], extra["filament_colour"] = _parse_filament_metadata(p.setting)
slots[slot].append(
UnifiedPreset(id=str(p.id), name=p.name, source="local", **extra),
)
preset.filament_type, preset.filament_colour = _parse_filament_metadata(p.setting)
if slot in ("process", "filament"):
# Precise compatibility link — the slicer's own compatible_printers
# list, captured at import time. Lets the SliceModal filter the
# process / filament dropdowns by the selected printer without
# falling back to its name-suffix heuristic.
preset.compatible_printers = _parse_compatible_printers(p.compatible_printers)
slots[slot].append(preset)
return slots
def _parse_compatible_printers(raw: str | None) -> list[str] | None:
"""``LocalPreset.compatible_printers`` stores a JSON array of printer-preset
names. Return the parsed list, or ``None`` on missing / malformed data so
the SliceModal falls back to its name-suffix heuristic for that preset."""
if not raw:
return None
try:
data = json.loads(raw)
except (ValueError, TypeError):
return None
if not isinstance(data, list):
return None
names = [s for s in data if isinstance(s, str) and s.strip()]
return names or None
def _parse_filament_metadata(setting_json: str | None) -> tuple[str | None, str | None]:
"""Extract first-slot ``filament_type`` and ``filament_colour`` from a
stored preset JSON. OrcaSlicer stores both as arrays (per-extruder) — we
+9
View File
@@ -31,6 +31,14 @@ class UnifiedPreset(BaseModel):
the multi-color flow by matching against the source 3MF's per-slot type
and color. Populated when the underlying preset JSON exposes them; left
as ``None`` on bundled profiles where colour is a runtime spool attribute.
``compatible_printers`` is the slicer's own list of printer-preset names a
process / filament preset declares itself valid for. Populated for the
local tier (stored at import time); left ``None`` for cloud (no per-preset
detail is fetched — rate limits) and standard (the sidecar's bundled
listing doesn't expose it). The SliceModal uses it to filter the
process / filament dropdowns by the selected printer (#1325), falling back
to a name-suffix heuristic when it is ``None``.
"""
id: str
@@ -38,6 +46,7 @@ class UnifiedPreset(BaseModel):
source: Literal["cloud", "local", "standard"]
filament_type: str | None = None
filament_colour: str | None = None
compatible_printers: list[str] | None = None
class UnifiedPresetsBySlot(BaseModel):
+28
View File
@@ -642,3 +642,31 @@ class TestBundleRoutes:
):
await sp.delete_slicer_bundle("missing", db=MagicMock(), _=None)
assert exc.value.status_code == 404
class TestParseCompatiblePrinters:
"""``compatible_printers`` exposed for local process / filament presets so
the SliceModal can filter the dropdowns by the selected printer (#1325)."""
def test_parses_json_array(self):
raw = '["Bambu Lab X1 Carbon 0.4 nozzle", "Bambu Lab X1 0.4 nozzle"]'
assert sp._parse_compatible_printers(raw) == [
"Bambu Lab X1 Carbon 0.4 nozzle",
"Bambu Lab X1 0.4 nozzle",
]
def test_none_and_empty_return_none(self):
assert sp._parse_compatible_printers(None) is None
assert sp._parse_compatible_printers("") is None
assert sp._parse_compatible_printers("[]") is None
def test_malformed_json_returns_none(self):
assert sp._parse_compatible_printers("not json") is None
# A JSON value that isn't an array is treated as absent, not an error.
assert sp._parse_compatible_printers('"a string"') is None
def test_drops_non_string_and_blank_entries(self):
assert sp._parse_compatible_printers('["X1C", 5, "", " ", "A1"]') == [
"X1C",
"A1",
]