print_queue.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. from datetime import datetime
  2. from typing import Annotated, Literal
  3. from pydantic import BaseModel, PlainSerializer
  4. # Custom serializer to ensure UTC datetimes have Z suffix
  5. def serialize_utc_datetime(dt: datetime | None) -> str | None:
  6. if dt is None:
  7. return None
  8. # Add Z suffix to indicate UTC
  9. return dt.isoformat() + "Z"
  10. UTCDatetime = Annotated[datetime | None, PlainSerializer(serialize_utc_datetime)]
  11. class PrintQueueItemCreate(BaseModel):
  12. printer_id: int | None = None # None = unassigned, user assigns later
  13. target_model: str | None = None # Target printer model (mutually exclusive with printer_id)
  14. target_location: str | None = None # Target location filter (only used with target_model)
  15. required_filament_types: list[str] | None = None # Required filament types for model-based assignment
  16. filament_overrides: list[dict] | None = None # Filament overrides for model-based assignment
  17. # Either archive_id OR library_file_id must be provided
  18. archive_id: int | None = None
  19. library_file_id: int | None = None
  20. scheduled_time: datetime | None = None # None = ASAP (next when idle)
  21. require_previous_success: bool = False
  22. auto_off_after: bool = False # Power off printer after print completes
  23. manual_start: bool = False # Requires manual trigger to start (staged)
  24. # Persistent "Print Anyway" acknowledgement (#1698-followup). When set,
  25. # PrintModal already showed the deficit warning and the user confirmed,
  26. # so the scheduler does not re-flag this item on the next tick.
  27. skip_filament_check: bool = False
  28. # AMS mapping: list of global tray IDs for each filament slot
  29. # Format: [5, -1, 2, -1] where position = slot_id-1, value = global tray ID (-1 = unused)
  30. ams_mapping: list[int] | None = None
  31. # Plate ID for multi-plate 3MF files (1-indexed, None = auto-detect/plate 1)
  32. plate_id: int | None = None
  33. # Print options
  34. bed_levelling: bool = True
  35. flow_cali: bool = False
  36. vibration_cali: bool = True
  37. layer_inspect: bool = False
  38. timelapse: bool = False
  39. use_ams: bool = True
  40. # Nozzle offset calibration — dual-nozzle printers only (#1682). Default True
  41. # matches BambuStudio's default; the MQTT layer ignores the flag on
  42. # single-nozzle printers so the wire value stays "skip" there.
  43. nozzle_offset_cali: bool = True
  44. # Auto-print G-code injection
  45. gcode_injection: bool = False
  46. # Batch: create multiple copies (creates a batch if > 1)
  47. quantity: int = 1
  48. # Existing batch to add this item into. When set, the item's batch_id is
  49. # populated on insert so the queue UI groups it with its siblings. Used by
  50. # the multi-plate auto-batch flow and by the "Group as batch" action.
  51. batch_id: int | None = None
  52. # Project to associate the resulting archive with
  53. project_id: int | None = None
  54. cost_center_id: int | None = None
  55. estimated_cost: float | None = None
  56. class PrintQueueItemUpdate(BaseModel):
  57. printer_id: int | None = None
  58. target_model: str | None = None # Target printer model (mutually exclusive with printer_id)
  59. target_location: str | None = None # Target location filter (only used with target_model)
  60. filament_overrides: list[dict] | None = None # Filament overrides for model-based assignment
  61. position: int | None = None
  62. scheduled_time: datetime | None = None
  63. require_previous_success: bool | None = None
  64. auto_off_after: bool | None = None
  65. manual_start: bool | None = None
  66. ams_mapping: list[int] | None = None
  67. plate_id: int | None = None
  68. # Print options
  69. bed_levelling: bool | None = None
  70. flow_cali: bool | None = None
  71. vibration_cali: bool | None = None
  72. layer_inspect: bool | None = None
  73. timelapse: bool | None = None
  74. use_ams: bool | None = None
  75. nozzle_offset_cali: bool | None = None
  76. # Auto-print G-code injection
  77. gcode_injection: bool | None = None
  78. cost_center_id: int | None = None
  79. estimated_cost: float | None = None
  80. # H2C dual-nozzle-rack slicer pick (#1780). list[int] per-filament
  81. # physical nozzle position IDs from BambuStudio's project_file MQTT
  82. # body; sent back to the printer verbatim on dispatch.
  83. nozzle_mapping: list[int] | None = None
  84. class PrintQueueItemResponse(BaseModel):
  85. id: int
  86. printer_id: int | None # None = unassigned
  87. target_model: str | None = None # Target printer model for model-based assignment
  88. target_location: str | None = None # Target location filter for model-based assignment
  89. required_filament_types: list[str] | None = None # Required filament types for model-based assignment
  90. filament_overrides: list[dict] | None = None # Filament overrides for model-based assignment
  91. waiting_reason: str | None = None # Why a model-based job hasn't started yet
  92. archive_id: int | None # None if library_file_id is set (archive created at print start)
  93. library_file_id: int | None # For queue items from library files
  94. cost_center_id: int | None = None
  95. estimated_cost: float | None = None
  96. position: int
  97. scheduled_time: UTCDatetime
  98. require_previous_success: bool
  99. auto_off_after: bool
  100. manual_start: bool
  101. # True when the dispatch scheduler last evaluated this item and the
  102. # assigned spool could not satisfy at least one slot's required grams
  103. # (#1496). Display-only — the ▶ click recomputes deficit against live
  104. # spool state.
  105. filament_short: bool = False
  106. # User has acknowledged "Print Anyway" — scheduler skips the deficit check
  107. # for this item (#1698-followup).
  108. skip_filament_check: bool = False
  109. ams_mapping: list[int] | None = None
  110. plate_id: int | None = None # Plate ID for multi-plate 3MF files
  111. # Print options
  112. bed_levelling: bool = True
  113. flow_cali: bool = False
  114. vibration_cali: bool = True
  115. layer_inspect: bool = False
  116. timelapse: bool = False
  117. use_ams: bool = True
  118. nozzle_offset_cali: bool = True
  119. status: Literal["pending", "printing", "completed", "failed", "skipped", "cancelled"]
  120. started_at: UTCDatetime
  121. completed_at: UTCDatetime
  122. error_message: str | None
  123. created_at: UTCDatetime
  124. # Nested info for UI (populated in route)
  125. archive_name: str | None = None
  126. archive_thumbnail: str | None = None
  127. # True when the linked archive has been soft-deleted (its files are gone
  128. # from disk). In that case the *archive_name* / *archive_thumbnail* /
  129. # downstream metadata fields are intentionally left None so the frontend
  130. # doesn't 404-storm the now-missing thumbnail / plates / plate-thumbnail
  131. # endpoints (#1348 follow-up). Frontends can render a "source deleted"
  132. # badge based on this flag.
  133. archive_deleted: bool = False
  134. library_file_name: str | None = None # Name of library file (if library_file_id is set)
  135. library_file_thumbnail: str | None = None # Thumbnail of library file
  136. printer_name: str | None = None
  137. print_time_seconds: int | None = None # Estimated print time from archive or library file
  138. filament_used_grams: float | None = None # Estimated print weight from archive or library file
  139. filament_type: str | None = None # e.g. "PLA", "PETG" (from archive/library file)
  140. filament_color: str | None = None # e.g. "#FFFFFF" (from archive/library file)
  141. layer_height: float | None = None # e.g. 0.2 (from archive/library file)
  142. nozzle_diameter: float | None = None # e.g. 0.4 (from archive/library file)
  143. sliced_for_model: str | None = None # e.g. "P1S" (from archive/library file)
  144. # Build plate type (e.g. "Textured PEI Plate") so the user knows which
  145. # plate to mount on the printer (#1281). Per-plate accurate on multi-plate
  146. # 3MFs: when `plate_id` is set, the value is the matching plate's
  147. # `curr_bed_type` rather than the archive-level first-plate default.
  148. bed_type: str | None = None
  149. # User tracking (Issue #206)
  150. created_by_id: int | None = None
  151. created_by_username: str | None = None
  152. # Batch grouping
  153. batch_id: int | None = None
  154. batch_name: str | None = None
  155. # Shortest-job-first scheduling
  156. been_jumped: bool = False
  157. # Auto-print G-code injection
  158. gcode_injection: bool = False
  159. # H2C dual-nozzle-rack slicer pick (#1780). Surface for any future
  160. # "edit print → choose nozzle" UI; null on every model except O1C2
  161. # uploads from BambuStudio.
  162. nozzle_mapping: list[int] | None = None
  163. class Config:
  164. from_attributes = True
  165. class PrintQueueReorderItem(BaseModel):
  166. id: int
  167. position: int
  168. class PrintQueueReorder(BaseModel):
  169. items: list[PrintQueueReorderItem]
  170. class PrintQueueBulkUpdate(BaseModel):
  171. """Bulk update multiple queue items with the same values."""
  172. item_ids: list[int]
  173. # Fields to update (all optional - only set fields are applied)
  174. printer_id: int | None = None
  175. scheduled_time: datetime | None = None
  176. require_previous_success: bool | None = None
  177. auto_off_after: bool | None = None
  178. manual_start: bool | None = None
  179. # Print options
  180. bed_levelling: bool | None = None
  181. flow_cali: bool | None = None
  182. vibration_cali: bool | None = None
  183. layer_inspect: bool | None = None
  184. timelapse: bool | None = None
  185. use_ams: bool | None = None
  186. nozzle_offset_cali: bool | None = None
  187. # Auto-print G-code injection
  188. gcode_injection: bool | None = None
  189. cost_center_id: int | None = None
  190. estimated_cost: float | None = None
  191. class PrintQueueBulkUpdateResponse(BaseModel):
  192. """Response for bulk update operation."""
  193. updated_count: int
  194. skipped_count: int # Items that were not pending
  195. message: str
  196. class PrintBatchCreate(BaseModel):
  197. """Create a batch, either empty (multi-plate pre-batch flow) or by
  198. assigning existing pending queue items into it (manual "Group as batch")."""
  199. name: str
  200. archive_id: int | None = None
  201. library_file_id: int | None = None
  202. # Existing pending queue items to assign to this batch. None / empty for
  203. # the empty-batch flow (client passes the returned id on subsequent
  204. # addToQueue calls).
  205. item_ids: list[int] | None = None
  206. class PrintBatchUngroupResponse(BaseModel):
  207. """Response after ungrouping a batch."""
  208. ungrouped_count: int
  209. message: str
  210. class PrintBatchResponse(BaseModel):
  211. """Response for a print batch with progress stats."""
  212. id: int
  213. name: str
  214. archive_id: int | None = None
  215. library_file_id: int | None = None
  216. quantity: int
  217. status: str
  218. created_at: UTCDatetime
  219. created_by_id: int | None = None
  220. created_by_username: str | None = None
  221. # Derived counts
  222. pending_count: int = 0
  223. printing_count: int = 0
  224. completed_count: int = 0
  225. failed_count: int = 0
  226. cancelled_count: int = 0
  227. class Config:
  228. from_attributes = True