mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user