slicer_presets.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. """Pydantic schemas for the unified slicer-presets endpoint.
  2. The SliceModal pulls printer/process/filament options from three sources, in
  3. priority order: cloud (the user's Bambu Cloud account), local (DB-backed
  4. imported profiles), and standard (slicer-bundled stock profiles). The endpoint
  5. returns all three lists with name-based dedup applied so each preset appears
  6. exactly once across the response.
  7. """
  8. from typing import Literal
  9. from pydantic import BaseModel
  10. CloudStatus = Literal["ok", "not_authenticated", "expired", "unreachable"]
  11. class UnifiedPreset(BaseModel):
  12. """A single printer/process/filament preset with its source.
  13. The ``id`` shape varies by source:
  14. - cloud → Bambu Cloud setting_id (e.g. ``"PFUS9ac902733670a9"``)
  15. - local → stringified DB row id from ``local_presets``
  16. - standard → preset name as written in the bundled JSON (the slicer
  17. resolves bundled profiles by name during inheritance walk)
  18. The frontend treats ``id`` as opaque; the slice dispatch path uses
  19. ``(source, id)`` to fetch / pass the preset content to the sidecar.
  20. ``filament_type`` and ``filament_colour`` are populated for the filament
  21. slot only — they let the SliceModal pre-pick a preset per plate slot in
  22. the multi-color flow by matching against the source 3MF's per-slot type
  23. and color. Populated when the underlying preset JSON exposes them; left
  24. as ``None`` on bundled profiles where colour is a runtime spool attribute.
  25. ``compatible_printers`` is the slicer's own list of printer-preset names a
  26. process / filament preset declares itself valid for. Populated for the
  27. local tier (stored at import time); left ``None`` for cloud (no per-preset
  28. detail is fetched — rate limits) and standard (the sidecar's bundled
  29. listing doesn't expose it). The SliceModal uses it to filter the
  30. process / filament dropdowns by the selected printer (#1325); when it is
  31. ``None`` the modal falls back to matching the preset name against the
  32. ``@BBL <code>`` printer-model registry.
  33. ``inherits`` is the preset a printer preset was saved from, for the local
  34. and OrcaSlicer Cloud tiers. A printer preset saved under a name of its own
  35. ("Bambu Lab H2D 0.4 nozzle - Skew") is listed in no process's
  36. ``compatible_printers`` and its name says nothing reliable about the
  37. model, so the SliceModal matches against this parent as well (#3250).
  38. OrcaSlicer Cloud filament presets carry it too: one saved with only the
  39. keys the user changed takes its material from this parent (#3403).
  40. """
  41. id: str
  42. name: str
  43. source: Literal["orca_cloud", "cloud", "local", "standard"]
  44. filament_type: str | None = None
  45. filament_colour: str | None = None
  46. compatible_printers: list[str] | None = None
  47. inherits: str | None = None
  48. class UnifiedPresetsBySlot(BaseModel):
  49. """Three slots in the order Bambu Studio / OrcaSlicer use."""
  50. printer: list[UnifiedPreset] = []
  51. process: list[UnifiedPreset] = []
  52. filament: list[UnifiedPreset] = []
  53. class UnifiedPresetsResponse(BaseModel):
  54. """Every tier carries its full preset list — no cross-tier dedup.
  55. Priority order: ``local > orca_cloud > cloud > standard``. The order
  56. drives auto-pick (first non-empty tier wins, name-lookup walks tiers
  57. in this order, filament scoring tiebreaks by per-tier bonus) and
  58. determines the visual rendering order of the SliceModal's optgroups,
  59. but a name that exists in multiple tiers appears in EACH of their
  60. groups so the user can pick any source.
  61. ``cloud_status`` / ``orca_cloud_status`` let the frontend show a banner
  62. explaining why a cloud tier is empty when the user expected to see it
  63. (signed out / token expired / network down). Each tier has its own
  64. status because they can fail independently.
  65. """
  66. orca_cloud: UnifiedPresetsBySlot = UnifiedPresetsBySlot()
  67. cloud: UnifiedPresetsBySlot = UnifiedPresetsBySlot()
  68. local: UnifiedPresetsBySlot = UnifiedPresetsBySlot()
  69. standard: UnifiedPresetsBySlot = UnifiedPresetsBySlot()
  70. cloud_status: CloudStatus = "ok"
  71. orca_cloud_status: CloudStatus = "ok"
  72. class LoadedSpoolPreset(BaseModel):
  73. """The filament profile Bambuddy recorded for a slot (``slot_preset_mappings``).
  74. ``tray_info_idx`` is the filament id the slot was configured with alongside
  75. it. The SliceModal compares it with the tray's live id the same way the
  76. slot card does, so a row that outlived a spool swap isn't trusted (#3216).
  77. """
  78. preset_id: str
  79. preset_name: str
  80. preset_source: str
  81. tray_info_idx: str | None = None
  82. class LoadedSpoolTray(BaseModel):
  83. """One AMS slot or external holder, as the printer reports it right now."""
  84. ams_id: int
  85. tray_id: int
  86. tray_type: str | None = None
  87. tray_sub_brands: str | None = None
  88. tray_color: str | None = None
  89. tray_info_idx: str | None = None
  90. # Firmware's presence bit. A non-RFID spool it can't identify is present
  91. # (True) but has no tray_type, which reads as "loaded, unconfigured".
  92. exists: bool | None = None
  93. state: int | None = None
  94. saved_preset: LoadedSpoolPreset | None = None
  95. class LoadedSpoolUnit(BaseModel):
  96. id: int
  97. is_ams_ht: bool
  98. trays: list[LoadedSpoolTray]
  99. class LoadedSpoolPrinter(BaseModel):
  100. id: int
  101. name: str
  102. # Short model name ("X1C", "H2D") — the form the slicer's @BBL tags use.
  103. model: str | None = None
  104. ams: list[LoadedSpoolUnit] = []
  105. # Only holders that currently have a spool in them.
  106. external: list[LoadedSpoolTray] = []
  107. # How many holders the printer has, loaded or not: two on a dual-nozzle
  108. # printer, whose holders are labelled left and right.
  109. external_holders: int = 0
  110. class LoadedSpoolsResponse(BaseModel):
  111. """Connected printers and what is loaded in them, for the SliceModal (#3172)."""
  112. printers: list[LoadedSpoolPrinter] = []