slicer_plugin.py 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """Schemas for the slicer plugin's upload API."""
  2. from typing import Literal
  3. from pydantic import BaseModel, Field, model_validator
  4. class SlicerPluginPresets(BaseModel):
  5. """The preset names the slicer had selected when it sliced."""
  6. printer: str | None = Field(default=None, max_length=255)
  7. process: str | None = Field(default=None, max_length=255)
  8. filaments: list[str] = Field(default_factory=list, max_length=64)
  9. class SlicerPluginSource(BaseModel):
  10. """Where the file came from -- what the file itself cannot tell us."""
  11. slicer: str | None = Field(default=None, max_length=50)
  12. slicer_version: str | None = Field(default=None, max_length=50)
  13. plugin_version: str | None = Field(default=None, max_length=50)
  14. project_name: str | None = Field(default=None, max_length=255)
  15. presets: SlicerPluginPresets | None = None
  16. # A hash of the model's geometry, so a later slice of the same model can
  17. # find this one however its file is named.
  18. model_fingerprint: str | None = Field(default=None, max_length=128, pattern=r"^[A-Za-z0-9:_-]+$")
  19. class SlicerPluginIntent(BaseModel):
  20. """What the user asked for when they sent the file."""
  21. action: Literal["library", "queue"] = "library"
  22. folder_id: int | None = None
  23. # Queue only. 1-based, as in the 3MF (Metadata/plate_<n>.gcode).
  24. plate_id: int | None = Field(default=None, ge=1)
  25. copies: int = Field(default=1, ge=1, le=100)
  26. printer_id: int | None = None
  27. target_model: str | None = Field(default=None, max_length=50)
  28. manual_start: bool = False
  29. # Required by POST /queue/ when billing is enabled; ignored otherwise.
  30. cost_center_id: int | None = None
  31. @model_validator(mode="after")
  32. def _one_destination(self) -> "SlicerPluginIntent":
  33. if self.printer_id is not None and self.target_model:
  34. raise ValueError("Cannot specify both printer_id and target_model")
  35. return self
  36. class SlicerPluginManifest(BaseModel):
  37. """The JSON part sent alongside the file.
  38. Unknown keys are ignored so an older Bambuddy accepts a newer plugin's
  39. additions; a change it must understand bumps ``schema_version`` instead.
  40. """
  41. schema_version: int
  42. # Chosen by the plugin, one per send; a retry reuses it.
  43. upload_id: str = Field(min_length=8, max_length=64, pattern=r"^[A-Za-z0-9-]+$")
  44. source: SlicerPluginSource = Field(default_factory=SlicerPluginSource)
  45. intent: SlicerPluginIntent = Field(default_factory=SlicerPluginIntent)
  46. class SlicerPluginLibraryFile(BaseModel):
  47. id: int
  48. filename: str
  49. file_type: str
  50. file_size: int
  51. thumbnail_path: str | None
  52. duplicate_of: int | None = None # an active library file with the same bytes
  53. class SlicerPluginQueueResult(BaseModel):
  54. queue_item_ids: list[int]
  55. batch_id: int | None = None # set when copies > 1
  56. class SlicerPluginUploadResponse(BaseModel):
  57. upload_id: str
  58. library_file: SlicerPluginLibraryFile
  59. # With action "queue": what was queued, or why nothing was. The file is in
  60. # the library either way, so the user can still queue it from there.
  61. queue: SlicerPluginQueueResult | None = None
  62. queue_error: str | None = None
  63. warnings: list[str] = []
  64. # True when this is the stored answer to an earlier request with the same
  65. # upload_id, not a new upload.
  66. replayed: bool = False
  67. class SlicerPluginInfoResponse(BaseModel):
  68. bambuddy_version: str
  69. manifest_versions: list[int]
  70. can_upload: bool
  71. can_queue: bool