mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
When scheduling a print to "any printer" (model-based assignment), users can now override the 3MF's original filament choices per slot. The override dropdown shows compatible filaments loaded across all printers of the selected model, filtered by type and nozzle (H2D). The scheduler matches against overridden type/color instead of the original 3MF values, preferring printers with exact color matches. Backend: - New GET /printers/available-filaments endpoint (aggregates loaded filaments across printers of a model, includes extruder_id for dual-nozzle nozzle-aware filtering) - New filament_overrides JSON column on print_queue table - Scheduler applies overrides before AMS mapping, clears tray_info_idx on overridden slots so color matching takes priority - Printer selection prefers printers with most override color matches Frontend: - New FilamentOverride component with per-slot override dropdowns - Type-filtered options (PLA slots only show PLA) - Nozzle-aware filtering (H2D: only shows filaments on correct extruder) - Integrated into PrintModal for model-based queue mode Tests: - 11 backend unit tests (color match counting, override application) - 12 frontend tests (rendering, type/nozzle filtering, interactions) i18n: All 6 locales (en, de, fr, it, ja, pt-BR)
147 lines
5.6 KiB
Python
147 lines
5.6 KiB
Python
from datetime import datetime
|
|
from typing import Annotated, Literal
|
|
|
|
from pydantic import BaseModel, PlainSerializer
|
|
|
|
|
|
# Custom serializer to ensure UTC datetimes have Z suffix
|
|
def serialize_utc_datetime(dt: datetime | None) -> str | None:
|
|
if dt is None:
|
|
return None
|
|
# Add Z suffix to indicate UTC
|
|
return dt.isoformat() + "Z"
|
|
|
|
|
|
UTCDatetime = Annotated[datetime | None, PlainSerializer(serialize_utc_datetime)]
|
|
|
|
|
|
class PrintQueueItemCreate(BaseModel):
|
|
printer_id: int | None = None # None = unassigned, user assigns later
|
|
target_model: str | None = None # Target printer model (mutually exclusive with printer_id)
|
|
target_location: str | None = None # Target location filter (only used with target_model)
|
|
required_filament_types: list[str] | None = None # Required filament types for model-based assignment
|
|
filament_overrides: list[dict] | None = None # Filament overrides for model-based assignment
|
|
# Either archive_id OR library_file_id must be provided
|
|
archive_id: int | None = None
|
|
library_file_id: int | None = None
|
|
scheduled_time: datetime | None = None # None = ASAP (next when idle)
|
|
require_previous_success: bool = False
|
|
auto_off_after: bool = False # Power off printer after print completes
|
|
manual_start: bool = False # Requires manual trigger to start (staged)
|
|
# AMS mapping: list of global tray IDs for each filament slot
|
|
# Format: [5, -1, 2, -1] where position = slot_id-1, value = global tray ID (-1 = unused)
|
|
ams_mapping: list[int] | None = None
|
|
# Plate ID for multi-plate 3MF files (1-indexed, None = auto-detect/plate 1)
|
|
plate_id: int | None = None
|
|
# Print options
|
|
bed_levelling: bool = True
|
|
flow_cali: bool = False
|
|
vibration_cali: bool = True
|
|
layer_inspect: bool = False
|
|
timelapse: bool = False
|
|
use_ams: bool = True
|
|
|
|
|
|
class PrintQueueItemUpdate(BaseModel):
|
|
printer_id: int | None = None
|
|
target_model: str | None = None # Target printer model (mutually exclusive with printer_id)
|
|
target_location: str | None = None # Target location filter (only used with target_model)
|
|
filament_overrides: list[dict] | None = None # Filament overrides for model-based assignment
|
|
position: int | None = None
|
|
scheduled_time: datetime | None = None
|
|
require_previous_success: bool | None = None
|
|
auto_off_after: bool | None = None
|
|
manual_start: bool | None = None
|
|
ams_mapping: list[int] | None = None
|
|
plate_id: int | None = None
|
|
# Print options
|
|
bed_levelling: bool | None = None
|
|
flow_cali: bool | None = None
|
|
vibration_cali: bool | None = None
|
|
layer_inspect: bool | None = None
|
|
timelapse: bool | None = None
|
|
use_ams: bool | None = None
|
|
|
|
|
|
class PrintQueueItemResponse(BaseModel):
|
|
id: int
|
|
printer_id: int | None # None = unassigned
|
|
target_model: str | None = None # Target printer model for model-based assignment
|
|
target_location: str | None = None # Target location filter for model-based assignment
|
|
required_filament_types: list[str] | None = None # Required filament types for model-based assignment
|
|
filament_overrides: list[dict] | None = None # Filament overrides for model-based assignment
|
|
waiting_reason: str | None = None # Why a model-based job hasn't started yet
|
|
archive_id: int | None # None if library_file_id is set (archive created at print start)
|
|
library_file_id: int | None # For queue items from library files
|
|
position: int
|
|
scheduled_time: UTCDatetime
|
|
require_previous_success: bool
|
|
auto_off_after: bool
|
|
manual_start: bool
|
|
ams_mapping: list[int] | None = None
|
|
plate_id: int | None = None # Plate ID for multi-plate 3MF files
|
|
# Print options
|
|
bed_levelling: bool = True
|
|
flow_cali: bool = False
|
|
vibration_cali: bool = True
|
|
layer_inspect: bool = False
|
|
timelapse: bool = False
|
|
use_ams: bool = True
|
|
status: Literal["pending", "printing", "completed", "failed", "skipped", "cancelled"]
|
|
started_at: UTCDatetime
|
|
completed_at: UTCDatetime
|
|
error_message: str | None
|
|
created_at: UTCDatetime
|
|
|
|
# Nested info for UI (populated in route)
|
|
archive_name: str | None = None
|
|
archive_thumbnail: str | None = None
|
|
library_file_name: str | None = None # Name of library file (if library_file_id is set)
|
|
library_file_thumbnail: str | None = None # Thumbnail of library file
|
|
printer_name: str | None = None
|
|
print_time_seconds: int | None = None # Estimated print time from archive or library file
|
|
filament_used_grams: float | None = None # Estimated print weight from archive or library file
|
|
|
|
# User tracking (Issue #206)
|
|
created_by_id: int | None = None
|
|
created_by_username: str | None = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class PrintQueueReorderItem(BaseModel):
|
|
id: int
|
|
position: int
|
|
|
|
|
|
class PrintQueueReorder(BaseModel):
|
|
items: list[PrintQueueReorderItem]
|
|
|
|
|
|
class PrintQueueBulkUpdate(BaseModel):
|
|
"""Bulk update multiple queue items with the same values."""
|
|
|
|
item_ids: list[int]
|
|
# Fields to update (all optional - only set fields are applied)
|
|
printer_id: int | None = None
|
|
scheduled_time: datetime | None = None
|
|
require_previous_success: bool | None = None
|
|
auto_off_after: bool | None = None
|
|
manual_start: bool | None = None
|
|
# Print options
|
|
bed_levelling: bool | None = None
|
|
flow_cali: bool | None = None
|
|
vibration_cali: bool | None = None
|
|
layer_inspect: bool | None = None
|
|
timelapse: bool | None = None
|
|
use_ams: bool | None = None
|
|
|
|
|
|
class PrintQueueBulkUpdateResponse(BaseModel):
|
|
"""Response for bulk update operation."""
|
|
|
|
updated_count: int
|
|
skipped_count: int # Items that were not pending
|
|
message: str
|