queueing plates we cannot map (#2552)
The override panel disappeared for a multi-plate selection in Any [model]
mode, but only once the dialog had been opened before -- which the reporter
saw as "after the file was queued or printed". The filament requirements are
keyed on the selected plate, which is null as soon as two plates are ticked.
On a cold cache the modal cannot yet tell the file is multi-plate and fetches
the whole file's requirements for one render; the panel rendered from that
union. On a warm cache it knows from the first render, the whole-file fetch
never runs, and the panel had nothing to render. Visibility was decided by a
cache race, and the "working" case listed filaments from plates the user had
not selected.
Model mode now renders one panel per selected plate from that plate's own
requirements, and each queued plate carries only the overrides for the slots
it prints, so a colour forced on one plate no longer blocks another.
Reviewing the per-plate machinery turned up four more holes, all closed here:
a manual tray pick survived a change of printer, and a global tray id names a
different spool on a different machine; a plate whose filaments could not be
read was indistinguishable from one needing none and was queued with neither
mapping nor forced colours, so Print now waits for every selected plate to
answer and names the one it cannot read; the insufficient-filament check still
weighed the whole file against a mapping the plates no longer use, and now
follows what each plate dispatches, summing demand per tray; and the
per-printer tray editor no longer appears for a multi-plate fan-out, where its
choices were collected and then discarded.
Selecting several plates hid the filament mapping panel but did not stop the
modal sending a mapping. With no single plate selected it fell back to the
whole file's filament list -- the union of every plate -- and matched against
that. Tray assignment is stateful, so where plate 1 prints red on slot 1 and
plate 2 prints red on slot 2, slot 1 claimed the only red spool and slot 2 fell
through to a type-only match on black. That one mapping went out with every
plate, and the scheduler uses a stored mapping verbatim, so plate 2 printed in
the wrong colour -- decided by a panel the user never saw.
Fetch each selected plate's requirements and map them separately: one panel per
plate, named after it, with its own tray overrides, and each queue item carries
its own plate's mapping. A fan-out across several printers would be a panel per
plate per printer, so those items carry no mapping and the scheduler maps each
plate against the printer it picks. Model mode is unchanged -- no printer means
no trays to map onto.
The tray matcher existed twice and this needed a third caller, so extract it
once and have both existing paths delegate; its 62 tests pass unchanged.
The bug only reproduces with a realistic query cache -- the shared test harness
sets gcTime: 0, which evicts the union and makes the modal look innocent -- so
the new modal tests bring their own client.
The app was built dark-first, so hundreds of hardcoded Tailwind semantic
text/icon utilities at light shades (text-amber-400, text-blue-300, ...) had
no dark: variant. With darkMode:'class' they applied in light theme too,
producing washed-out text on pale tints and white cards — including the three
reported spots (AMS Drying banner, Archives no-3MF warning, debug-logging
banner). Give each a theme-aware pair: a darker readable shade in light theme
with the original pinned to dark:, so dark theme is unchanged. ~100 files.
The bambu-* CSS-variable palette (self-correcting) and the dark-only SpoolBuddy
kiosk are left untouched. Plain text-white is already theme-aware via the
existing index.css .text-white override, so it needed no changes.
Editing a queue item that was created with "Any of model X" left the
printer selection area completely blank — the assignmentMode was
initialised to 'model' from queueItem.target_model, but the three
model-mode props (onAssignmentModeChange, onTargetModelChange,
onTargetLocationChange) were gated behind !isEditing. That flipped
modelAssignmentAvailable to false in PrinterSelector and hid the mode
toggle, the model dropdown, AND the location filter; combined with the
assignmentMode === 'printer' gate on the printer list, the whole
selector rendered empty.
Users hit this whenever they queued something to "Any of model X" and
then wanted to change the target model / location — the only workaround
was delete + re-queue.
Fix: drop the !isEditing gate on all three PrinterSelector props. The
submit path already handles both flavours (target_model+target_location
with printer_id=null vs. printer_id with the target fields nulled), so
un-gating the UI just surfaces the machinery that was already there.
Edit is still only offered on pending items, so the mode-flip can't race
an in-flight dispatch.
PrintModal carried a useEffect that reset scheduleOptions.gcodeInjection
to false whenever mode === 'create' AND effectiveQuantity <= 1. The
comment claimed the checkbox only renders for quantity > 1, but the
actual render gate in ScheduleOptions is just hasGcodeSnippets — no
quantity check. So with snippets configured + quantity = 1 (the OP
scenario): user clicks the checkbox, React updates state to true,
the parent's useEffect immediately sees effectiveQuantity <= 1 and
resets to false, and the checkbox appears un-clickable. Edit-queue-
item mode worked because mode !== 'create' short-circuited the reset.
Drop the effectiveQuantity <= 1 clause from the reset. Keep the
!settings?.gcode_snippets half as the legitimate cleanup for the
"admin removes all snippets while modal is open" case. The scheduler
reads item.gcode_injection per queue item regardless of batch size,
so single prints can inject too.
New scheduler stage that heats the bed (and the chamber, on supported
printers) and holds at temperature before each queued print starts —
the heat-soak engineering filaments need for adhesion and warp
control. Bambuddy waits between FTP upload and start_print, so the
soak runs while the printer is otherwise idle. M191 is silently
ignored by Bambu firmware, so doing this at the orchestration layer
is the only place it works.
Resolution order at dispatch:
1. PrintQueueItem.preheat_override ∈ {inherit, on, off}.
'off' skips entirely; 'inherit' falls back to the global
preheat_enabled toggle; 'on' forces the stage even when the
global is off.
2. chamber_target = item.preheat_chamber_target_override
?? max(filament_map[normalize(t.tray_type)] for loaded slots)
?? 0.
Mixed PA+PLA picks PA's 50 (max-across-slots — PA's chamber
requirement is binding, PLA doesn't suffer being warm). PLA-only
derives 0 and skips the chamber phase automatically.
3. Three hardware tiers for chamber heat:
- Active chamber heater (H2C/H2D/H2D Pro/H2S/X2D/X1E) → M141 +
chamber-sensor wait
- Chamber sensor only (X1C/P2S) → no M141, passive bed-radiation
wait with hard max-wait cap
- No chamber sensor (P1S/P1P/A1/A1 Mini) → bed + soak timer only
4. Airduct flap (H2C/H2D/H2D Pro/H2S/X2D/P2S) auto-switches to
match the chamber target — heating mode for engineering
filaments, cooling mode for PLA. Bambu firmware does NOT
auto-switch the flap with M141, so without this an ABS print
on a previously-cooling flap fights the open exhaust, and a
PLA print on a previously-hot flap recirculates ABS heat.
Idempotent: only fires set_airduct_mode when current ≠ desired.
Settings → Workflow → Queue & Dispatch → Preheat & Heat Soak card:
master enable toggle (default off — disabled installs see no change),
per-filament chamber-target editor (replaces a single global int that
shipped in the first cut and couldn't serve PA + PLA in the same
config), preheat_max_wait_seconds, preheat_soak_seconds. The Print
Options panel in PrintModal gets a Preheat sub-section with the
tri-state Inherit/On/Off control and an optional chamber-target
override input.
DB migration: PrintQueueItem gains preheat_override VARCHAR(10)
DEFAULT 'inherit' and preheat_chamber_target_override INTEGER NULL.
Idempotent via _safe_execute. Existing rows behave exactly as before
the migration.
Best-effort throughout: printer drops, refused M141 or set_airduct,
missing bed temp, lost MQTT state mid-wait all log and return cleanly.
Normal upload + start path runs after this returns regardless.
Reported off-list by a corporate supporter running a multi-operator
farm shift. Backend already rejects double-sends with HTTP 409 (see
background_dispatch._dispatch at lines 283-290), so no double-print
was possible, but PrinterSelector consulted only PrinterStatus.state
({IDLE, FINISH, FAILED}) to decide whether a printer was selectable.
PRINT_START is the only signal that flips the printer out of IDLE, so
during the upload + print-command + firmware-ack window the card
stayed clickable and operators only found out after submitting.
New useDispatchedPrinterIds hook subscribes to the existing
background-dispatch WebSocket event (already consumed for the toast
overlay), collects printer_ids from dispatched_jobs + active_jobs,
and exposes them via useSyncExternalStore so every PrinterSelector
instance shares one snapshot. OR'd into isPrinterBusy; badge label
flips to "Dispatching..." instead of a misleading "Idle".
No backend change — the reservation already exists, the UI just
didn't reflect it. No behaviour change for add-to-queue / edit-queue
modes (disableBusy stays false for those).
Two tightly-coupled deliverables in one drop -- a new AMS Filament Backup
status/control surface, and the #1766 fix that depends on it.
Added -- AMS Filament Backup status + control
- Parse bit 18 of top-level print.cfg into PrinterState.ams_filament_backup
on every push_status. Verified against OrcaSlicer source
(DeviceManager.cpp:4961) and a live H2D ON/OFF capture. Tri-state
(None = A1 family / pre-cfg push) preserves today's behaviour.
- Hold-timer guard (3 s) prevents stale frames from flickering the badge
back to the printer's old cfg after a user-initiated toggle.
- POST /printers/{id}/ams-backup toggle, set_ams_filament_backup() client
method calling _set_print_option("auto_switch_filament", enabled).
- GET /printers/{id}/inventory-remain endpoint exposes the same map the
dispatcher uses (internal and Spoolman modes both work uniformly).
- Small icon badge in the printer card's "Filaments" section header
(placement reads as printer-wide because the cfg bit is printer-wide,
not per-AMS). Click to toggle, success toast.
- 5 i18n keys x 11 locales for the badge UI.
Fixed -- #1766: prefer_lowest didn't pick lowest, ignored backup state
- Backend gate in _compute_ams_mapping_for_printer: coerce prefer_lowest
to False when status.ams_filament_backup is False; log the skip.
- New effectivePreferLowest(setting, backup) helper applied at every
frontend sort entry point: single-printer PrintModal, multi-printer
hook per-printer, PrinterSelector InlineMappingEditor, FilamentMapping
standalone editor (the last had NO preferLowest awareness at all
before this change).
- New preferLowestSortKey(f, inventoryByTrayId) mirrors backend's two-tier
key exactly, including the banding tie-break (regular AMS < AMS-HT <
external) so the client-side pre-compute matches the dispatch-time pick.
An earlier draft used a flat `amsId * 4 + trayId` priority which gave
external slots (ams_id = -1) a NEGATIVE priority -- caught in code
review before commit.
- Settings -> Filament -> "Prefer lowest remaining filament" gets an
explanatory note about the printer-side AMS Backup dependency, with
i18n key in all 11 locales.
Restructures the queue page around three tabs (Queue / History / Timeline)
and adds first-class batch grouping plus a real time-based timeline.
Queue tab
- Layout toggle: Sort by Position (flat list) or Group by Printer (per-
printer section cards with aggregate count / time / weight headers).
- Batch grouping: pending items sharing a batch_id render as a single
collapsible row with aggregate stats; children draggable within the
batch only. Per-batch collapse state in localStorage.
- Multi-drag: dragging any selected row moves all selected items as a
contiguous block via DragOverlay (+N ghost).
- Selection bar gains a Group as batch action when 2+ ungrouped items
are selected. Ungroup lives on the batch parent row.
History tab
- Two-line rich rows: filament color swatch + weight + type, user
attribution, inline error message on failed / skipped rows.
- Responsive 1 / 2 / 3 column grid so a long history uses available
width instead of stretching one row per line.
- Batch siblings group into a collapsible parent with status-rollup
chips (3 OK / 1 failed / etc).
- Thumbnail hover preview shows the full image at 192x192 next to the
small thumb.
Timeline tab
- Replaces the hourly-list view with a Gantt swimlane: one row per
printer (plus per target_model and unassigned), horizontal hour
axis, jobs as bars positioned by start time and sized by duration.
- Live NOW marker.
- Only committed schedules are rendered: currently printing items,
pending items with scheduled_time, and pending ASAP behind an active
print. Staged (manual_start), waiting (waiting_reason), and ASAP
jobs on idle printers are filtered out.
- 24h rolling window with 12h step controls.
- Per-bar tooltip with start, end, progress, batch name.
Backend
- POST /queue/batches creates a batch, optionally assigning existing
pending item_ids (manual grouping) or returning an empty batch the
client can attach to subsequent /queue/ POSTs.
- POST /queue/batches/{id}/ungroup clears batch_id from all members
(skipping items the caller does not own) and deletes the batch row
when no members remain.
- POST /queue/ accepts an optional batch_id and validates that the
batch exists, is active, and the caller may modify it. The existing
quantity > 1 auto-batch path still fires when no batch_id is sent.
PrintModal
- When N plates from one source are queued in a single submission
(model assignment or single printer), the modal pre-creates a batch
and passes its id to each addToQueue call so multi-plate jobs land
grouped automatically. Falls back to ungrouped items if the batch
pre-create fails.
Before this change, the Re-print and Schedule modals' per-filament
slot dropdown hid every slot whose extruder didn't match the filament's
slicer-assigned nozzle. On H2D with AMS A+C on the left and B on the
right, an L-assigned filament could only pick A or C and an R-assigned
one could only pick B — locking the user out of cross-extruder picks
even when they'd intentionally loaded the required filament into the
other AMS.
The slicer wasn't the source of the asymmetry: BambuStudio Desktop,
OrcaSlicer Desktop, and the Bambuddy sidecar all produced identical
filament_map values for the same source 3MF. Bambuddy's UI filter was.
Drop the f.extruderId === item.nozzle_id clause in FilamentMapping's
loadedFilaments filter. Single-nozzle and FTS short-circuits stay; the
L/R row badge stays as a hint to the slicer's intent. Printer firmware
decides at start-print whether the cross-extruder ams_mapping is valid.
Round 2 fixed the model-mode FilamentOverride: tray_info_idx →
sub-brand, plus a material-disambiguated colour name from a new
/inventory/colors/by-material endpoint. The printer-mode panel that
renders the same 3MF (FilamentMapping) was reading the same raw
fields — item.type for the required label, getColorName(item.color)
for the swatch tooltip — and was not touched, so picking "Specific
Printer" still showed "Required: PLA - Black" for a slice the
"Any H2D" branch already labelled "Bambu PLA Matte - Charcoal".
Extract the three-query resolution machinery from FilamentOverride
into a shared hook useFilamentLabels (returns positional
{resolvedName, colorLabel} per slot). Both panels call it; both
read the same labels. The hook also owns extractMaterialHint so the
"strip leading brand token" rule has one source of truth.
FilamentMapping required-side now reads {resolvedName} instead of
{item.type}; swatch tooltip reads `Required: {resolvedName} -
{colorLabel}` instead of `Required: {item.type} -
getColorName(item.color)`.
The Print Queue's schedule dialog hid the per-slot "Force color match"
checkbox when a specific printer was picked, even though the scheduler
in print_scheduler.py:535 honours force_color_match regardless of how
the queue item was created. Model-mode ("Any A1") rendered
FilamentOverride which carries the checkbox; printer-mode rendered
FilamentMapping which had no force-match UI at all. Pure UI gap.
Extend FilamentMapping to accept optional forceColorMatch +
onForceColorMatchChange props, mirroring FilamentOverride's signature,
and render the same <Palette>-iconed checkbox under each filament row
when a handler is wired. PrintModal/index.tsx passes the existing
forceColorMatch state through — same object both modes write into so
toggling between modes preserves the user's selection. No new i18n
keys (printModal.forceColorMatch already ships in all 11 locales).
When the user clicked Print Anyway on a filament-deficit warning, the
acknowledgement was one-shot. The route cleared manual_start and
filament_short, then the next scheduler tick re-ran
compute_deficit_for_queue_item against identical spool state, found
the same deficit, and re-set both flags. The item bounced between
"user said anyway" and "scheduler re-blocked" — every Play click
returned 409, every confirm got rolled back on the next tick.
Add a persistent acknowledgement flag on the queue item:
- New column `skip_filament_check` on print_queue. SQLite + Postgres
migration branched on is_sqlite() so Postgres doesn't reject
DEFAULT 0 on BOOLEAN.
- PrintQueueItemCreate + PrintQueueItemResponse schemas + the
TypeScript types carry the field.
- POST /print-queue/{id}/start with skip_filament_check=true now
ALSO sets item.skip_filament_check = True (not just clearing
manual_start / filament_short).
- PrintScheduler._block_on_filament_deficit short-circuits to
False — no compute, no flag-setting, no notification — when
item.skip_filament_check is True. We trust the operator's
decision and stop fighting them.
- PrintModal at queue-creation time threads
skip_filament_check=true into the create payload when the user
clicks Print Anyway on the frontend deficit warning, so a print
that was warned-then-acknowledged at add-to-queue time goes in
pre-acknowledged — scheduler never blocks it on first tick.
Flag is not auto-cleared on spool swap by design: if remaining is
now sufficient, the check returns no deficit anyway, so the flag
is moot. Auto-clearing would add lifecycle complexity without
changing behaviour.
AMS Backup awareness (the other half of the discussion) intentionally
NOT included — verified the H2D's bit-26 of print.cfg toggles with
the printer-side AMS Backup setting, but the X1C's cfg has a
different shape entirely and verifying every model family isn't
realistic. Silently under-warning would be worse than always
per-slot. The check stays single-slot for now.
Reporter on a multi-printer farm with 40+-plate runs needed to walk to
the printer with the right physical plate, but the queue and the
scheduling modal didn't surface curr_bed_type the way the archive card
already did.
New utils/threemf_tools.extract_bed_type_from_3mf helper (per-plate) so
both the queue API and the /plates endpoint can return per-plate values.
PrintQueueItemResponse.bed_type populated from archive.bed_type /
library_file.file_metadata['bed_type'] as the default, then overridden
per-plate via the helper when item.plate_id is set. /archives/{id}/plates
(and library equivalent) include bed_type in each plate object.
Per-plate accuracy matters because archive.bed_type is captured at
ingest as only the first plate's value (services/archive.py:235) -
a 40-plate 3MF mixing PEI + Engineering returns PEI at the archive
level for every plate. The helper re-reads the 3MF and returns the
truth.
Frontend: queue card meta row + PlateSelector per-plate row + PrintModal
header all render bed icon + canonical label via the existing
getBedTypeInfo() helper, same as the archive card.
Bambuddy's project_file MQTT payload hardcoded "nozzle_offset_cali": 2 (skip),
giving users on H2D / H2D Pro / H2C / X2D no way to control the same toggle
BambuStudio exposes. Critical for diamond-nozzle setups that must keep the
calibration off.
start_print() now takes a nozzle_offset_cali kwarg; the value is encoded as
1 (run) or 2 (skip) and gated on is_dual_nozzle so single-nozzle machines
always send 2 even if a stale flag arrives. The kwarg threads through
printer_manager, both background_dispatch sites, and print_scheduler so
every dispatch path respects the per-item setting.
print_queue gains a nozzle_offset_cali column (DEFAULT TRUE, is_sqlite()
branch for Postgres BOOLEAN). Settings default key default_nozzle_offset_cali
defaults to TRUE to match BambuStudio. Schemas updated across print_queue,
library FilePrintRequest, archive ReprintRequest, settings.
PrintModal renders the new toggle only when the selected printer is dual-
nozzle (printer-mode: nozzle_count===2; model-mode: DUAL_NOZZLE_MODELS).
SettingsPage default-print-options row + QueuePage bulk-edit tri-state both
hide unless any registered printer is dual-nozzle. Labels reuse the existing
settings.default* keys so the only new i18n strings are
settings.defaultNozzleOffsetCali / Desc and queue.bulkEdit.nozzleOffsetCali
- real translations in all 11 locales.
The FTS routes any AMS slot to either extruder, so AMS info reports
bits 8-11 = 0xE (uninitialized) and ams_extruder_map ends up empty.
The print modal's per-nozzle dropdown filter then hides every loaded
slot, leaving the user with an empty filament dropdown.
Detection: parse print.device.fila_switch from MQTT push_status into a
new FilaSwitchState dataclass on PrinterState; surface it through the
GET /printers/{id}/status response as a nullable FilaSwitchResponse.
Frontend: useFilamentMapping and FilamentMapping skip the per-extruder
filter when fila_switch.installed is true. Slots currently fed into a
track display an [L]/[R] routing badge in the dropdown so the user
can see where the FTS is currently routing them.
Tests: 4 backend unit (TestFilamentTrackSwitchDetection), 2 backend
integration (status route), 2 hook regression, 2 component regression.
The "Print" button on a printer card (and drag-drop-onto-card) used
FileUploadModal to persist the file as a LibraryFile, then dispatched
through POST /library/files/{id}/print. The LibraryFile row + disk file
were left behind after every one-off print, polluting File Manager with
entries the user never asked to save.
FilePrintRequest.cleanup_library_after_dispatch (default False) opts
into post-dispatch cleanup. When set, _run_print_library_file stages
db.delete(lib_file) in the same transaction as archive_print so a
mid-flight FTP / start_print failure rolls both back cleanly, commits
together, then unlinks the library disk file + thumbnail after commit
succeeds. External library files (is_external=True) are never touched.
Only the Printers-page Direct-Print PrintModal sets the flag. Every
other api.printLibraryFile caller (File Manager Print, Project Detail
Print) leaves it unset — their entries are there by user intent.
Also moves formatPrintName out of PrintersPage.tsx into a new
utils/printName.ts module — fa1c46d9 (#881) exported it inline so its
test could import it, tripping react-refresh/only-export-components.
* feat: print files directly from project view (closes#930)
Show printable files from linked library folders directly in the project
detail page, with Print Now and Add to Queue buttons per file. Removes
the detour through the File Manager for common reprint workflows.
Per-model start/end G-code snippets configurable in Settings (Workflow
tab). Queue items get "Inject G-code" toggle — scheduler injects
snippets into a temp 3MF copy before FTP upload. Supports Farmloop,
SwapMod, AutoClear, Printflow 3D and similar bed-clearing systems.
Original files are never modified.
When multiple AMS spools match the same type/color criteria, an optional
setting now prefers the spool with the lowest remaining filament. This
helps consume partial spools before starting new ones. Sorting applies
to all matching paths: queue scheduler, print modal, and multi-printer
mapping. Unknown remain values (-1) sort to end.
Stagger option now available when printing directly to multiple printers,
not just in queue mode. Prints are automatically queued with staggered
start times using group size/interval from Settings. New "Require
plate-clear confirmation" setting lets farm users disable per-printer
plate confirmations so queued prints start automatically on finished
printers.
Also fixes settings API type parsing for require_plate_clear (boolean),
stagger_group_size and stagger_interval_minutes (integer) — without this,
saved values returned as strings would cause the settings toggle to
always show enabled and trigger a permanent save loop.
Queue page redesign: compact stats bar replaces summary cards, color-
coded left borders for status scanning, collapsible history with
condensed rows, and a new production schedule timeline view (#823)
showing estimated completion times grouped by hour with filter tabs
and day navigation. i18n keys added for all 7 languages.
Fix plate thumbnails returning 401 in print modal when auth is enabled
(missing stream token). Fix schedule calendar picker opening off-screen
(hidden input positioned with sr-only instead of near the date field).
When adding a print to the queue for multiple printers, users can now
enable "Stagger printer starts" to avoid power spikes from simultaneous
bed heating. Configurable group size and interval — first group starts
immediately (ASAP) or at scheduled time, subsequent groups get offset
scheduled_time values. No backend queue/scheduler changes — leverages
existing scheduled_time field.
Also adds a dedicated Queue tab in Settings (stagger defaults + auto-
drying moved from Filament tab), i18n keys for all 7 locales, frontend
and backend tests.
Multi-plate 3MF files now support selecting a subset of plates to queue
via checkboxes, instead of the binary "one plate" or "all plates" toggle.
In add-to-queue mode, each plate gets a checkbox for multi-select with a
Select All / Deselect All toggle. Reprint and edit modes remain single-select.
Bambu Lab firmware treats PA-CF, PA12-CF, and PAHT-CF as interchangeable,
but the print scheduler and filament override UI used exact string matching.
A 3MF requiring PA-CF wouldn't match PA12-CF in the AMS, leaving the
override dropdown disabled and preventing scheduler assignment.
Add a filament type equivalence system (both backend and frontend) so
these PA variants are treated as compatible in scheduler assignment,
AMS slot matching, force color match validation, and the override dropdown.
Also fix the "Force color match" checkbox click target being too wide
(entire row instead of just the checkbox/label) by changing flex to
inline-flex on the label element.
* Added "Force Color Match" feature to filament override
* Update settings.py
* Update settings.py
* Label updates for warnings and checkbox case
* Initial plan
* Add noMatchingMaterial i18n translations to all 7 locale files
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Use {{material}} interpolation in noMatchingMaterial; populate from filament_overrides with getColorName
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Updates for PR#625 comments
* Added "Force Color Match" feature to filament override
* Update settings.py
* Update settings.py
* Label updates for warnings and checkbox case
* Initial plan
* Add noMatchingMaterial i18n translations to all 7 locale files
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Use {{material}} interpolation in noMatchingMaterial; populate from filament_overrides with getColorName
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Updates to PrintersPage and QueuePage
* Update QueuePage.tsx
* Rebase to 0.2.2b3
* Fix compile errors with FilamentOverride
* Updates for PR#625 comments
https://github.com/maziggy/bambuddy/pull/625#pullrequestreview-3908738261
* Fix force color match default, loadedFilamentTypes bug, remove dead translations
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
Co-authored-by: MartinNYHC <mz@v8w.de>
Multi-plate 3MF files can now be queued in one action via a "Queue All
Plates" toggle in the plate selector (add-to-queue mode only). Each
plate becomes a separate queue entry, individually editable.
Fix: "Clear Plate & Start Next" no longer appears when all pending
queue items are staged (manual_start). The prompt only shows when
there are auto-dispatchable items the scheduler will actually start.
When printing from the file manager, the print modal showed all printers
including busy ones. Selecting a busy printer resulted in a failed send.
The printer selector now fetches live status for each printer and shows
state badges (Idle, Printing, Paused, etc.). In reprint mode, non-available
printers (anything other than IDLE, FINISH, FAILED) are grayed out and
not selectable. "Select all" skips them. Queue/edit modes still allow
selecting busy printers since the job will wait.
All filament mapping dropdowns (single-printer, multi-printer, and
"Print to Any" model-based) showed only the base material type (e.g.
"PLA") without distinguishing subtypes like "PLA Basic" vs "PLA Matte",
making entries with different subtypes but same color look identical.
Backend: Add tray_sub_brands to available-filaments response and include
it in the dedup key so different subtypes of the same color appear as
separate entries.
Frontend: Show tray_sub_brands in FilamentMapping, FilamentOverride, and
PrinterSelector dropdowns, falling back to base type when unset. Add
traySubBrands field to LoadedFilament interface.
The per-printer InlineMappingEditor showed filaments from both nozzles
on H2D printers. The single-printer FilamentMapping dropdown was fixed
in 29e9593 but the multi-printer code path was missed. Added nozzle_id
filtering to both auto-match logic and dropdown options via shared
autoMatchFilament() and filterFilamentsByNozzle() functions in
amsHelpers.ts, with 10 regression tests.
FTP upload: reduce chunk size from 1MB to 64KB for smooth progress bar
updates (~1s intervals instead of 20+ second gaps). Skip voidresp() for
all printer models — H2D delays the 226 response by 30+ seconds after
data transfer, causing a hang at 100%. Add transfer speed and TLS
handshake timing to logs for diagnosing slow connections.
Print/Schedule modal: widen from max-w-lg (512px) to max-w-2xl (672px)
to accommodate long filament profile names like "PLA Support for PETG
PETG Basic @Bambu Lab H2D 0.4 nozzle".
* Add printer card print flow with drag-drop and reusable LibraryUploadModal
Extract inline upload modal from FileManagerPage into a reusable
LibraryUploadModal component. Add a Print button and drag-drop zone
to printer cards that upload files to the library and open PrintModal
with the printer pre-selected. Only .gcode/.gcode.3mf files are
accepted for printing. PrintModal hides printer selector when a
printer is provided via initialSelectedPrinterIds prop.
* Add printer compatibility check for drag-drop and upload-to-print flows
Validates sliced_for_model from file metadata against the target printer model
before opening the print modal, preventing users from printing with incompatible files.
* Improve FileUploadModal: auto-close on success, inline errors, file validation, and i18n
- Close modal automatically after successful upload instead of showing summary
- Show printer compatibility errors inline in the modal instead of closing
- Add validateFile and accept props to restrict file types for print flow
- Add onFileUploaded error return to prevent modal close on incompatible files
- Internationalize all hardcoded error/warning strings across 6 locales
* Clean up incompatible files from library and fix print button i18n
- Delete uploaded file from library when printer compatibility check fails
in both drag-drop and modal upload flows
- Fix print button tooltip to use existing common.print i18n key
- Fix accept attribute to use .gcode,.3mf for proper browser support
* Add printer information modal accessible from card 3-dot menu
Replace inline IP/serial display at bottom of printer card with a
dedicated "Printer Information" modal opened via the card menu. The
modal shows model, status, state, IP address (copyable), serial number
(copyable), WiFi signal, firmware, developer mode, nozzle count, SD
card, auto-archive, total print hours, location, and added date, along
with the printer image. Includes full i18n support across all 6 locales
and accessibility attributes (role="dialog", aria-modal).
* Extract getPrinterImage and getWifiStrength into shared printer utils
---------
Co-authored-by: MartinNYHC <mz@v8w.de>
When scheduling a print to "Any {model}", a redundant "Target Model"
dropdown appeared even though the G-code is already sliced for a
specific printer model. Changing it would lead to print failures.
Hide the dropdown when slicedForModel is known — the tab label already
communicates the target. The dropdown still appears as a fallback for
legacy files without model metadata.
All backend timestamps used datetime.now() (server local time) or the
deprecated datetime.utcnow(). The frontend's parseUTCDate() assumes
timestamps without timezone indicators are UTC and appends 'Z', so
stored timestamps were off by the timezone offset when the container's
timezone wasn't UTC.
Backend: replaced datetime.now() and datetime.utcnow() with
datetime.now(timezone.utc) across 16 files (~80 call sites) for all
database fields and DB comparisons. Cosmetic timestamps (filenames,
user-facing local time formatting) intentionally left as local time.
Frontend: replaced 13 new Date(backendTimestamp) calls with
parseUTCDate() across 8 files to correctly interpret UTC timestamps.
The "Power off printer when done" checkbox in the print modal and the
auto power off tri-state toggle in the bulk edit modal were accessible
to all users regardless of permissions. Users without printers:control
can now no longer enable auto power off — controls are disabled and
visually dimmed.
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)
This allows users sending particularly large prints to slow printers such as the P1-series to start a print and then move onto another task in Bambuddy immediately (such as starting more prints on more printers). It also gives the user visibility into what's happening instead of a loading indicator appearing for an indefinite period of time.
The new toast-based UI uses websockets to update in real time. It will also appear for other users / instances of Bambuddy, not just the user who started the prints, allowing more transparency and handling cases where the user closes the page and then comes back wanting to know the status of the dispatching.
fix: review fixes for background dispatch PR #408
- Restore missing imports in main.py (inventory, print_log, virtual_printers, mqtt_smart_plug_service)
- Guard voidresp() for A1 printers to prevent hang after upload
- Don't fail upload on voidresp() error since data transfer already completed
- Add ams_mapping to register_expected_print calls for Spoolman usage tracking
- Fix cancel_job TOCTOU race by using single lock acquisition
- Fix batch counter reset TOCTOU by re-checking condition inside second lock
- Add backgroundDispatch translations to fr.ts and pt-BR.ts
- Remove dead upload_progress_callback definitions
- Skip redundant "Print queued" toast in reprint mode (dispatch toast handles it)
- 5 backend tests: cancel_job single-lock TOCTOU, batch reset re-check,
job lifecycle
- 2 FTP regression tests: voidresp error handling (upload-loop fix),
A1 model voidresp skip
- 1 frontend test: reprint toast suppression
- CHANGELOG: background dispatch feature + test coverage entries
- README: add background dispatch to Scheduling & Automation
- Website: add feature item to Automation section
- Wiki: add Background Print Dispatch section to print-queue.md