Print Archiving¶
BamDude archives every print automatically — the 3MF file, an extracted thumbnail, parsed metadata, energy and timing data, and full provenance back to the library file or queue item that produced it. Archives are the system of record for what was actually printed: dedup, library usage stats, the queue dispatcher, and the printer-page cover all read from this table.
How Archiving Works¶
When a print starts, BamDude creates a print_archives row that mirrors the run from start to finish, then pulls the 3MF off the printer's SD card via FTP and attaches it to that row:
graph LR
A[Print starts] --> B[Create row<br/>status=printing]
B --> C[FTP-fetch 3MF]
C --> D[Parse + attach<br/>metadata, thumbnail]
B --> E[Print runs]
E --> F[Status -> completed/failed/cancelled<br/>fill duration + energy]
The row comes first, and the file catches up. Fetching the 3MF back off a printer is not quick: measured on a P1S, 22 MB took 8m40s while the printer was printing, because the file is read from the same SD card the print is reading from. (The identical fetch on an idle printer took 96 seconds.) Creating the row afterwards meant the print did not exist anywhere in BamDude until the download finished — no entry in Archives, no start notification, and the queue still showing the printer free. Worse, the smart-plug meter reading that a print's energy is measured against was taken at the end of that download, so everything the printer drew during it went uncounted, bed heating included.
If the FTP fetch fails, the row simply keeps file_path = "" — see 3MF download recovery below. The dispatcher creates exactly one archive per physical print and wires PrintQueueItem.archive_id to it inside the same transaction (post-b1: there's no longer a race between scheduler and dispatcher creating duplicate rows). Prints BamDude dispatched itself already have their row before the file is uploaded, so the flow above describes prints started elsewhere — the printer's screen, a slicer's Send to Printer, Bambu Cloud.
The 3MF has to be somewhere BamDude can reach
BamDude fetches it from the printer's SD card over FTP, so on most printers the card must be inserted. Without one, only the metadata reported over MQTT can be recorded; thumbnails and 3D preview are unavailable.
On X2D, P2S and the H2 family the file is also fetched from the printer's built-in storage when the card has nothing — including for prints started on the printer's own screen, which would otherwise leave an archive with nothing attached.
X2D / P2S firmware TLS quirk (handled automatically)
Some firmware trips over modern Python's default TLS 1.3 on the FTPS channel — X2D fails the implicit-FTPS handshake outright, P2S hits a session-reuse bug — which left their archive cards empty (no filament / layers / MakerWorld link / thumbnail). BamDude now caps the FTPS session to TLS 1.2 for these models so the 3MF download at print start connects and the archive populates. No configuration needed.
What Gets Archived¶
Each archive row carries the file, the parsed metadata, the run state, and full provenance back to whatever produced it.
| Field | Description |
|---|---|
file_path |
3MF copy at data/archive/<printer_id>/<timestamp>_<name>/<filename>.3mf. Empty string means the 3MF couldn't be fetched (fallback row) or was cleaned by retention. |
file_size |
Bytes on disk. |
thumbnail_path |
Extracted PNG from the slicer. Stays even after the 3MF is cleaned. For 3MFs sliced through the Docker slicer sidecar — which skips the desktop-only plate-PNG render — BamDude generates the missing plate thumbnails server-side (an isometric Bambu-green render of the embedded model, 512 px + 128 px) so the archive card isn't blank. |
source_3mf_path |
Original project 3MF when uploaded from the slicer (separate from the dispatched copy). |
| Field | Description |
|---|---|
print_name |
Slicer-set print name. |
filament_type, filament_color |
Primary filament(s) for the print. Colour prefers the inventory spool loaded on each AMS slot (per slot), falling back to the sliced colour for any slot without an assigned spool. |
filament_used_grams |
Total grams the slicer estimated. |
layer_height, total_layers |
Layer geometry. |
nozzle_diameter, nozzle_temperature, bed_temperature |
Hotend / bed setpoints. |
print_time_seconds |
Slicer's estimate. |
actual_time_seconds, time_accuracy |
Real duration (completed_at − started_at) and the estimate-vs-actual accuracy %, recorded on completed prints and backfilled for history. |
sliced_for_model |
Printer model the 3MF was sliced for, extracted from project metadata. |
makerworld_url, designer |
Auto-extracted from the 3MF when present. |
| Field | Description |
|---|---|
status |
printing, completed, failed, cancelled, stopped, or archived. See status badges. |
started_at, completed_at |
Wall-clock bounds of the run (NULL until they happen). |
failure_reason |
Short cause code (e.g. firmware_error). |
error_message |
Verbose diagnostic from the dispatcher / scheduler — shown on hover over the badge. |
energy_start_kwh, energy_kwh, energy_cost |
Per-print energy from the printer's smart plug. energy_start_kwh is captured at print start so the delta survives backend restarts mid-print. |
| Field | Description |
|---|---|
printer_id |
Which printer produced it. |
library_file_id |
The library_files row this archive was dispatched from. NULL for external prints (printer-screen, cloud, manual SD-card start). |
project_id |
Optional project the archive was assigned to. |
created_by_id |
User who triggered the print. |
subtask_id |
Printer-assigned subtask identifier observed in MQTT push_status — used as a fast match key by on_print_start. |
queue_id |
The printer_queues row the archive belongs to (every archive has one — external prints fall back to the printer's default queue so stats queries see them). |
batch_id |
UUID shared across all queue items dispatched together; survives queue cleanup so "how many of batch X completed?" still works after the live queue rows are gone. |
| Field | Description |
|---|---|
content_hash |
SHA256 of the bytes actually stored in the archive directory. |
source_content_hash |
SHA256 of the chain-root (unpatched) source. Always populated since 0.4.2: when no chain ancestor exists, this archive becomes the chain root and the column is seeded with its own content_hash. Patched variants of a library file inherit this from the library row's hash. Migration m039 backfills legacy NULLs to the same invariant. |
applied_patches |
JSON list of patch identifiers the dispatch pipeline applied before upload, e.g. ["mesh_mode_fast_check_off"]. Informational only — never used to infer disk-file state. |
Stored inside extra_data (JSON):
| Key | Description |
|---|---|
printable_objects |
{ id: name } dict from the 3MF — populated for both the modal and M623 skip-objects calls. |
gcode_label_objects |
Whether the slicer wrote per-object identifiers into the gcode. Bambu Studio doesn't emit this field, so missing → True (BS default). OrcaSlicer writes it explicitly. Added in 0.4.1. |
exclude_object |
Whether the slicer enabled object exclusion in the print profile. Added in 0.4.1. |
The skip-objects button in the printer view requires both gcode_label_objects and exclude_object to be true — otherwise sending M623 would fail at the firmware (the gcode lacks the per-object markers).
OrcaSlicer users
OrcaSlicer ships with both flags off by default. Enable Print Settings → Others → Label objects and Exclude objects before slicing. Re-slicing is required — you can't toggle these on an already-sliced 3MF. Bambu Studio users don't need to do anything; the defaults are already correct there.
Archive Status Badges¶
The Archives page renders a small status pill on each card. The common case (a finished print) shows nothing — the badge appears only when something interesting is going on.
| Badge | Status | Meaning |
|---|---|---|
| blue (pulsing) | printing |
The print is running on the printer right now. Click → jumps to the printer page. |
| (none) | completed |
The print finished successfully. No badge — keeps the grid clean. |
| red | failed |
The print started but ended in firmware-error state. Hover the badge to see the full error_message from the dispatcher. |
| red | cancelled / stopped |
The print was aborted — manual cancel from the printer, queue cancellation, or IDLE after RUNNING (treated as a user abort). Same red styling as failed. |
| grey | archived |
The 3MF was registered (typically via dispatch dedup) but the print never actually ran. No completed_at / failed_at. Rare — usually only seen when an upload was deduped against an existing file. |
Printed / Not Printed filters¶
The Archives page header carries two single-click chips — Printed and Not Printed — that quickly slice the list by whether a file has any successful print history yet:
| Chip | Shows | Useful for |
|---|---|---|
| Printed | Archives whose library_file_id is referenced by at least one completed archive (i.e. you've actually run this file successfully at least once). |
Reprint candidates — you've already validated the slice. |
| Not Printed | The opposite — files with no completed archive yet. |
The "what's still pending" pile from a multi-day batch, or library imports you haven't gotten around to printing. |
The chips are mutually exclusive (toggle one off to enable the other) and stack with the freeform search box + status filters above them. Not Printed pairs naturally with the Library Trash auto-purge Include never-printed toggle — flip Not Printed on first to see what you're about to purge.
Deduplication & Chain-of-Custody¶
BamDude deduplicates archives by their source content hash, not just the bytes on disk.
The reason: the dispatch pipeline can patch a 3MF before upload — for example, commenting out M970/M970.3 vibration-probe commands when the per-print "mesh-mode fast check" toggle is off. The patched file has a different content_hash than the original, so a naive hash dedup would treat every patched variant as a new design.
source_content_hash solves this:
- When BamDude dispatches a patched print, it stores the SHA256 of the unpatched source in
source_content_hashand the SHA256 of the bytes that landed on the SD incontent_hash. - The on-disk file at
file_pathis always the unpatched original. Patched bytes live only in/tmp/bamdude_patch_*for the FTP-upload window and are cleaned up by the dispatcher;archive/never holds a patched variant. This is what lets reprint re-run the patcher per-job and togglemesh_mode_fast_check/ gcode injection in either direction — the patcher'sM970regex only matches uncommented lines and couldn't undo a previously-baked patch if the on-disk source were post-patch. - Dedup queries use
effective_hash = COALESCE(source_content_hash, content_hash). Since 0.4.2 every new archive populatessource_content_hash(chain root or self-seed), soeffective_hashreadssource_content_hashdirectly for new rows;COALESCEstays as defence-in-depth. - Reprinting from an existing archive copies the unpatched file into a fresh archive directory — new
content_hashif the new run patches differently, but the samesource_content_hash, so reprint history stays linked to the original design. - External prints (started on the printer screen / cloud / manual SD start) get a one-SELECT lookup at archive creation: if any prior archive on any printer matches by
content_hashorsource_content_hash, the chain is inherited (cross-printer in 0.4.2 — was per-printer before).
The Archives page exposes a "duplicates" filter that groups rows by this effective hash. The "Original print" badge, the original_archive_id link, the detail-endpoint duplicates list, and library-file print counts all bind on source_content_hash with content_hash only as a defence fallback for legacy NULL rows. Consequence: a printer-A unpatched run + a printer-B mesh-mode-disabled run of the same library file are grouped together in the badge and share one on-disk file (see below).
Cross-printer file-on-disk dedup (0.4.2)¶
When BamDude archives a print whose effective_hash matches an existing archive on any printer, the new archive row reuses the existing on-disk path instead of writing another copy. Match is on the chain-root hash (COALESCE(source_content_hash, content_hash)) — every row sharing an unpatched origin shares one on-disk file regardless of which patches each individual dispatch applied. Net effect: printing the same library file on N printers with M different patch combinations still stores one copy on disk + N×M archive rows. delete_archive ref-counts shared file_paths and only removes bytes when the last referencing row is hard-deleted. The same dedup runs in attach_3mf_to_archive (background download-retry path), so a fallback archive whose 3MF lands later joins the existing chain instead of writing the printer-fetched (patched) bytes when an unpatched copy already exists.
If you upgrade from a pre-0.4.2 install where archive/ may still contain leftover patched copies from the prior semantics, run python scripts/prune_orphan_archive_files.py (default dry-run, --apply to delete) to reconcile the directory against the current DB references.
Migrations m009 + m039
source_content_hash and applied_patches were added in m009. Migration m039 (0.4.2) backfills source_content_hash = content_hash for legacy NULL rows so the always-populated invariant holds across upgrade.
Saving a printed file back into the library¶
An archive's 3MF can be copied into the library from the archive's own menu — pick a folder, and the file arrives read the same way an upload is: metadata, thumbnail, per-plate details and badges already filled in, rather than as an anonymous file.
⚠️ Saving the same print twice does not make a second copy. The library recognises identical content and hands back the copy already there, saying so rather than pretending it wrote something.
⚠️ An archive may have no file to copy. A print started from the printer's own screen whose 3MF could not be pulled has a real archive row and an empty file — the action explains that instead of failing quietly. The same applies to reprint and to opening in a slicer.
Read-only external folders are left out of the picker: the library refuses to write into them, and offering a choice that cannot work is worse than not offering it.
Library File Linking¶
When you print from the file manager, the resulting archive carries library_file_id pointing at the source library row. The library row, in turn, carries running stats:
print_count— number of completed prints from this file. Failed, cancelled and aborted runs don't count.last_printed_at— timestamp of the latest completed print.
These are live-updated when a print finishes and were retroactively backfilled from the archive history on first boot of the m014 migration (oldest matching library row wins when multiple files share a hash, so reimports don't steal attribution).
In the file manager, sort by most printed or least recently printed to find the obvious candidates to prune from your library.
External prints aren't linked
Prints started directly on the printer screen, from Bambu Cloud, or from a manual SD-card start have library_file_id = NULL. The chain-of-custody hash still dedups them, but they don't bump library stats — those stats reflect "prints dispatched through BamDude", not "prints that happened to use this design".
3MF Download Recovery¶
Every archive row starts with file_path = "" and gets its 3MF attached once BamDude has fetched it from the printer. That fetch can fail — the printer is slow, the network glitches, the file has already been moved on the SD card, the path doesn't match. When it does, the row keeps its empty file_path and gains extra_data["no_3mf_available"] = True, and is filled in retroactively.
There is no size limit on the 3MF — ftp_timeout bounds a stall, not a slow transfer
A print's 3MF comes back over the same SD card the print is reading from, so the transfer rate depends on what the printer is doing: 231 KB/s on an idle P1S, 43 KB/s on a printing one. ftp_timeout used to be applied to the whole download as well as to the socket, which quietly turned it into a limit on file size — at 30 seconds, anything past a few megabytes was abandoned mid-transfer and archived as "3MF unavailable". It now applies per socket operation: a connection that has gone quiet is dropped after the configured seconds, and one that keeps delivering is left to finish. Uploads to the printer are separately capped at 10 minutes, which ftp_timeout does not affect.
Empty file_path and no_3mf_available are not the same thing
The empty path is what the recovery triggers select on, and it is the normal state of a print that started ninety seconds ago. The flag means something narrower: an attempt was made and it failed. Only the flag raises the Archives banner below, which is why it is never set optimistically while a download is still running.
There are four recovery triggers — no periodic polling, so short prints aren't affected:
- Startup sweep — on server start, every
status='printing' AND file_path=''archive is retried once. Runs asasyncio.create_taskso the FastAPI lifespan isn't blocked. - Printer reconnect —
PrinterManager.connect_printerfiresretry_printer_archives(printer_id)after a successful connection. on_print_completelast-chance — right before SD cleanup runs at print end, BamDude tries one more download. The file is still on SD and the printer is no longer busy writing — highest-probability success window.- Manual —
POST /api/v1/archives/{id}/retry-download. The frontend exposes a "Retry 3MF download" menu item on the archive card, visible only whenfile_pathis empty.
Concurrent triggers don't race: a per-archive asyncio.Lock returns "in_progress" immediately if another retry is already running. Five distinct return statuses (recovered, already_has_file, in_progress, failed, error) map to clean toasts in the UI. The print-start download takes the same lock even though it doesn't come from this service — a printer reconnect part-way through it would otherwise open a second FTP session for a file already on its way, and attach a second copy on top of the first. So does the download for a print BamDude adopts at start-up (below).
While the row has no file yet:
- Thumbnails and 3D preview won't render — the 3MF doesn't exist locally yet.
- Skip-objects modal stays hidden — the object list is unknown until the file lands. As soon as recovery completes, the loaded object list is pushed into the printer's MQTT state so the modal works for the rest of the print, not just from the next restart.
- MQTT-reported metadata still gets recorded — filament use, layer counts, energy, timing all flow in even without the 3MF.
When the file lands, ArchiveService.attach_3mf_to_archive() fills the existing row in place: copies the file to a fresh archive dir, reparses the 3MF, extracts the thumbnail, fills content_hash / print_name / bed_type / all metadata fields, backfills cost / quantity / swap_compatible, and clears the no_3mf_available flag. plate_index is backfilled only when the row doesn't already carry one — a row created at print start takes it from live MQTT state, which knows which plate is actually running, and a multi-plate container cannot overrule that.
Archives-page banner — \"prints archived without thumbnails\"
When a recent print landed through the no-3MF fallback, the Archives page shows a one-time, dismissible banner explaining how to fix it. The usual cause is "Store sent files on external storage" being off in the slicer — so the printer's SD card never gets the .gcode.3mf, and BamDude has nothing to FTP-fetch (hence no thumbnail or 3D preview). This is the slicer-only variant of that setting, which the printer never reports over MQTT, so the connection diagnostic can't detect it — the banner is the only place BamDude can surface it. Turn the setting on in your slicer and future prints archive with full thumbnails; the banner won't reappear once dismissed.
A print that started while BamDude was off¶
Until 0.5.6, a print begun from the printer's screen or the slicer while BamDude was down left no trace at all — no archive, no queue row — because the only place an external print's archive is created is the print-start event, and on a fresh start BamDude deliberately does not treat the first "running" it sees as a start (doing so would re-archive a print already under way). Now the print that is running when BamDude comes up is adopted: it gets the same row an external print gets at start, the queue row is claimed, the 3MF and thumbnails are fetched through the recovery path above, and the archive card appears like any other.
Two figures on such a row are honest rather than exact, and are marked so:
- Start time is reconstructed once the 3MF arrives, from the slicer's estimate and the remaining time the printer reported when BamDude joined (
estimate − remainingbefore that moment). If the file never arrives, the start stays unknown rather than invented, and the print's duration is left out of the statistics. - Energy counts only from the moment BamDude joined — the plug's meter had no earlier reading — so the figure is marked approximate.
There is no late "print started" notification, and only the print in progress at start-up can be recovered; prints that finished during the outage are gone.
3MF Auto-Cleanup (0.4.1, drift-mode in 0.4.2)¶
The archive directory is the largest single chunk of disk BamDude owns — every print copies its 3MF in. The auto-cleanup feature lets you set a retention window: 3MF files belonging to designs that haven't been printed for N days are deleted, but the archive rows themselves stay so the history (thumbnails, costs, notes, energy data, project links) is preserved.
Configuring it¶
Settings → Printing → File Manager → "Auto-cleanup of stored 3MF files"
- Toggle — disabled by default. Gates the auto-tick only; manual runs (see below) work whether the toggle is on or off.
- Retention input — days, minimum 1, default 30.
- Live preview — shows what would be cleaned right now:
X archives in Y groups, Z MB. - Last / Next run cards — extracted into a shared
<LastNextRunCards>component reused by the library auto-purge. Shows "cleared 5 archive(s), 240 MB freed, 4 hours ago" + "in ~20 hours" so admins can see the schedule without grepping logs. After a server restart the in-memoryarchives_clearedcount is lost; the card shows "count was lost on restart — see logs" instead of a misleading zero (the persistentarchive_3mf_cleanup_last_runtimestamp survives, only the count goes).
Manual run — Archives page header (0.4.2)¶
The Archives page header has a Cleanup 3MFs button that opens the same cleanup with a per-run override:
- Days input — pre-seeded from the saved retention threshold, with a one-click "reset to {{days}}" link when you've dragged it. Range 1–3650.
- Live preview — recomputes whenever the days input changes (debounced).
- Works with auto-mode disabled — the toggle in Settings only gates the auto-tick. The manual dialog and the API endpoints behind it honour the configured retention threshold regardless. When auto is off the modal shows a small amber hint "Auto-mode is off — only manual runs will fire" but the Run button stays active.
- Manual runs reset the auto-cycle — a successful manual run stamps
archive_3mf_cleanup_last_runso the next auto-tick won't re-scan minutes later.
How it decides what to delete¶
Eligibility is evaluated per design, not per archive row. A "design" is a group of archives sharing the same effective_hash = COALESCE(source_content_hash, content_hash).
- The cutoff is the newest activity across the whole group. If you reprinted an old design 3 days ago, every archive copy of that design — even rows from months back — is kept. The intent is "old designs you've moved on from", not "old physical files".
- Skip rules — if any of these fire, the whole group is skipped:
- Any archive in the group is currently
status='printing'. - An active queue item (pending / printing / paused) references any archive in the group.
- The originating
library_filesrow still exists with a matching hash. The library is your deliberate source of truth — no need to wipe an archive copy when it's still on the source path. The skip-set is built from the always-populatedsource_content_hash(the canonical chain root), so a patched archive variant correctly maps back to the library row that produced it.
- Any archive in the group is currently
Plan-phase optimisation (0.4.2)¶
The plan phase first does a SQL-side GROUP BY effective_hash HAVING MAX(activity_ts) < cutoff and only loads full rows for buckets that actually fell out of retention. Healthy installs (most groups still hot) skip the heavy row load entirely — significant win on installs with 10k+ archives where the previous "load every row, group in Python" path was a noticeable hot spot during the daily sweep.
What happens during cleanup¶
For each eligible design group:
- The
.3mffile is removed. - Per-plate
.gcode.md5sidecars are removed. - The per-archive directory is removed if it ends up empty.
- Every archive row in the group has its
file_pathblanked to""together — consistent UI, no half-cleared groups. - The
thumbnail_pathand the row itself stay — history is preserved.
Re-printing a cleaned archive shows "3MF unavailable" in the same way as a fallback archive that never had a file. Re-upload from your library or slicer to print it again.
Schedule (drift-mode since 0.4.2)¶
| Aspect | Behaviour |
|---|---|
| Tick frequency | Every 15 min. Cheap — just consults settings + the persisted last-run timestamp. |
| Auto-run window | Runs when now - archive_3mf_cleanup_last_run >= 24 h. The 24h check is a last is not None guard — first run after enabling the toggle fires immediately because no last-run exists yet. |
| First-fire delay | The loop sleeps TICK_INTERVAL_SECONDS (15 min) before evaluating, so the first auto-run lands ~15 min after enabling the toggle (or ~15 min after server boot if the toggle was already on). Click "Run now" in Settings or in the Archives page modal to fire instantly — the manual run stamps the same timestamp and starts the 24h drift cycle. |
| Manual-run effect | A successful manual run stamps the same timestamp, so it postpones the next auto-tick by 24 h instead of stacking. |
| Toggle effect | Consulted every tick — flipping it on/off takes effect without a restart. Toggle gates the auto-tick only; manual runs always work. |
| Timezone | Independent of server-local time (the previous "midnight cron" anchored to the OS clock). |
The library auto-purge follows the same pattern (15 min tick, 24 h drift, manual runs reset). See Library Trash for its analog of the Last/Next run cards.
| Endpoint | Permission |
|---|---|
GET /archives/cleanup/status |
archives:read |
GET /archives/cleanup/preview?days=N |
archives:read |
POST /archives/cleanup/run?days=N |
archives:delete_all |
The optional ?days=N query param (1–3650, clamped) lets the manual dialog override the saved retention threshold without persisting it.
What changed in 0.4.2
The upstream-ported per-row archive auto-purge (which moved old archive rows to trash on a separate 365-day timer) was removed in 0.4.2 — see the trash docs for the rationale. The 3MF cleanup described above is the only auto-mechanism that touches archive storage now; manual delete → trash → restore → empty-trash is unchanged for "I want this row gone" workflows.
3D + G-code Preview¶
Open the preview from any archive card — click the layers badge in the bottom-right, or right-click → 3D Preview. List view exposes the same action via the row menu. A single modal hosts both views as tabs, sharing one plate panel + bed-volume wireframe so the eye can match what's drawn against what's reported.
3D Model tab¶
Three.js scene with proper Phong shading:
- Rotate — click and drag.
- Zoom — scroll wheel.
- Pan — right-click and drag.
- Build-volume wireframe — translucent box matching the printer's bed; pulled from the 3MF's
printer_settingsso an A1-mini archive renders against a 180×180 box, not a hardcoded 256³. - Wireframe / X-ray toggle — flip every mesh in the scene from solid to wireframe so thin walls, supports, and non-manifold edges are visible. Persists across modal opens via
localStorage. - Supported formats:
.stl,.3mf,.obj(OBJ via Three's stockOBJLoader, mounted alongside STL + the custom 3MF parser). - Theme-synced background — light SPA theme renders the scene against a light grey, dark theme keeps the off-black bed; the canvas no longer punches a black rectangle through a light modal.
For multi-plate prints the archive remembers which plate of the source 3MF was actually printed — the 3D preview, G-code preview, thumbnail, and per-plate slicer metadata (print time, filament weight, layers, printable objects) all reflect that plate. There is no plate picker on archives — the archive is a record of one specific print, not a browser. Migration m038 populates plate_index on historical rows and re-parses 3MFs where plate_index > 1 so older multi-plate archives gain the same correctness.
G-code tab¶
Renders the actual sliced toolpath layer-by-layer — what the printer physically extruded — using gcode-preview (WebGL).
- Dual-handle layer slider — separate Start and End ranges so you can crop both top and bottom (default range = full build). Useful for inspecting internal infill / specific layer ranges without losing the rest of the model. Chevron buttons step layer-by-layer.
- Play / pause + speed picker — animates extrusion along the toolpath at 1× / 2× / 4× / 8× selectable speeds. Pauses at end; press play again to restart from
Start. - Travel-moves toggle — show / hide G0 (non-extrusion) moves to diagnose stringing / oozing patterns. Persisted in
localStorage. - Export PNG — saves the current view (current Start / End range, current travels state) as a PNG with a filename like
<archive>_layers_42-198.png. Useful for tickets and project notes. - Streaming progress bar — for big multi-plate gcodes (>20 MB) the loading state shows live
4.2 / 12.5 MBinstead of just spinning, then a separate "Parsing G-code…" state once the bytes have landed. - Theme sync — same as the 3D tab.
- Bed wireframe auto-detected from the printer model the archive was sliced for (H2D → 350×320×325 mm, X1C/P1S → 256³, A1 → 256³, A1-mini → 180³, etc.). No configuration.
For source-only archives (project 3MFs exported from BambuStudio without slicing) the modal hides the G-code tab automatically — there's no toolpath to render. The 3D Model tab still works because it renders the geometry, not the toolpath.
The viewer URL carries the archive reference, so refreshing the page keeps you in the shell with the viewer re-rendering correctly. The preview reads from the local archive copy — if the 3MF isn't on disk (fallback or cleaned), the preview is unavailable until the file is recovered.
Re-print with AMS Mapping¶
Reprint opens the ordinary Print dialog with the archive's source file and plate. It compares the used channels with the destination printer's current sources — AMS or supported external feeds.
What the modal shows¶
The selected plate's required materials and colors, available slots, and nozzle bindings. Several plates have separate requirements and mappings: unused colors from other plates do not become requirements for this print.
Status indicators¶
Material and colour matches, a different colour, and empty or incompatible sources help you review the selection. Allowing another colour does not relax material or nozzle requirements. Allow match by base material, on by default, may use a resolvable family's filament_type instead of a custom profile name; with it off, a known profile variant remains required. A preview does not reserve a spool: the server checks the complete mapping again before start.
Auto-matcher + manual override¶
- Auto-matching looks for a complete mapping of every used channel; one physical slot cannot supply two channels at once.
- A manual slot choice preserves physical intent. Another printer or plate needs a new mapping review.
- Slot labels respect Custom AMS Labels; color names come from the catalog.
- Refresh AMS state in the dialog after changing a spool. Stale information does not permit an incompatible mapping to start.
- Repeats retain available feed and color rules. Actual sources are checked for the new attempt; old AMS numbers do not move arbitrarily between machines.
See Filament Routing for AMS, external-feed, dual-nozzle, and waiting examples.
Print options¶
The modal exposes the same options table as the new-print and queue modals — see Print Queue. Highlights:
| Option | Default | Description |
|---|---|---|
| Bed Levelling | Enabled | Auto-level before print |
| Flow Calibration | Disabled | Calibrate extrusion flow |
| Mesh-mode fast check | Inherits from printer setting | When off, BamDude's gcode patcher comments out M970/M970.3 vibration probes — see archive chain-of-custody |
| First Layer Inspection | Disabled | AI inspection of first layer |
| Timelapse | Disabled | Record timelapse video |
| Record to | Internal storage | Which medium keeps the recording. Shown only on a printer that has both built-in storage and a healthy SD card |
| G-code injection | Per-job | Inject custom G-code (see G-code Injection) |
File-type badge
Cards show a GCODE (green) or SOURCE (orange) badge. Only GCODE files carry AMS mapping data — SOURCE archives are slicer project files without embedded print settings, so the Reprint button asks you to load them in the slicer first.
Photo Attachments¶
Attach pictures to an archive — useful for documenting failures, showing finished parts, or pairing a print with a project photo.
- Auto camera snapshot on print complete — toggle Settings → General → Capture snapshot on print complete. When on, BamDude grabs a frame from the printer's camera the moment the print finishes and attaches it to the archive. When a timelapse was recording for that print (because you enabled it in the send dialog — BamDude never forces timelapse on), the finish photo is pulled from the timelapse's last frame instead: it captures the moment after the toolhead parks but before the bed drops, which a live-camera grab would miss. External cameras keep their own framing.
- Manual upload — drag-and-drop image files onto the archive detail page, or use the + Add Photo button in the photo strip. Multiple photos per archive are supported.
- Failure documentation — attaching a photo of a failed print pairs nicely with the archive's
failure_reason+error_messagefields, so the post-mortem is all in one place.
Timelapse Editor¶
BamDude attaches printer timelapses to the matching archive automatically and lets you trim, retime, and score them in the browser without leaving the shell.
Built-in capture¶
| Format | Printers | Handling |
|---|---|---|
| MP4 | X1, X1C, X1E, A1, A1 mini, H2D | Attached directly |
| AVI | P1S, P1P | Saved immediately + converted to MP4 in the background via ffmpeg (-threads 1, nice -n 19) — runs at low priority so your Pi doesn't choke on it |
The timelapse is available in the archive viewer the moment the print finishes; AVI re-encoding happens silently in the background.
Where the recording is looked for¶
The SD card first. When the card has nothing — which is where recordings go on a printer with built-in storage and no card inserted — BamDude looks in the printer's internal timelapse catalogue instead, and downloads from wherever the file actually is. Both the automatic scan after a print and the Scan for timelapse button do this.
That catalogue names the print each recording belongs to, so the video is matched to the right archive outright. The card carries no such information, which is why the SD path still has to match on timing and filename and can ask you when two are ambiguous.
On both media the printer also reports the full path of the recording it has just finished writing, and the automatic scan takes that file by name instead of assuming the one new recording is the right one. It is still only accepted if it appeared after this print started — otherwise a video recorded from the printer's own screen mid-print, which the printer would honestly report as its most recent, could be attached to the wrong job.
Which printers keep timelapses internally
Not the same set as those that keep models internally — a machine can do one and not the other, so BamDude checks the two capabilities separately and only asks for a catalogue the printer says it has.
Clearing recordings off the printer¶
Settings → General → Remove timelapses from the printer once saved. Off by default. With it on, a recording is deleted from the printer as soon as it has been attached to its archive — from the SD card or from built-in storage, whichever it was read from.
Only ever after the copy is safely saved. If the attach fails, or the printer cannot be reached, the recording stays where it is.
Why this is not on by default
Having a copy in BamDude is not the same as nobody needing the file on the machine — it can still be watched from the printer's own screen, or carried away on the card.
Editor controls¶
Open an archive with a timelapse → click the timelapse thumbnail → Edit in the viewer header.
| Control | Description |
|---|---|
| Trim handles (two) | Drag the two markers on the timeline to set start / end of the kept segment |
| Speed selector | 0.25× → 4× playback speed |
| Music dropdown | Upload an MP3 / WAV / M4A / AAC / OGG and adjust volume; preview is synced with video playback |
| Preview Play | Plays only the trimmed range with the music overlay applied |
| Save | Re-encodes via ffmpeg server-side and replaces the original timelapse with the edited version |
Manual upload + remove¶
For LAN-only printers or when the auto-attach picks the wrong timelapse:
| Action | When visible | Description |
|---|---|---|
| Upload Timelapse | No timelapse attached | Drag-drop or pick .mp4, .avi, or .mkv — AVI/MKV auto-convert to MP4 in the background |
| Remove Timelapse | Timelapse attached | Detach the file and clear the reference (file is deleted from disk) |
Source 3MF Upload¶
For prints started outside BamDude — slicer's Send to Printer button, Bambu Cloud, or a manual SD card start — the archive is created without an associated source 3MF (only the on-printer copy is fetched). The detail page exposes an Upload Source button that lets you attach the original 3MF post-hoc:
- Open the archive detail page.
- Click Upload Source 3MF in the action bar.
- Pick the original file from your slicer's export directory.
Once uploaded the file is stored in source_3mf_path (separate from the dispatched copy), and:
- 3D model + G-code previews become available for the archive.
- Re-print works through the standard AMS-mapping modal — the source 3MF carries the slicer's filament list.
- The archive's chain-of-custody is unaffected —
content_hashstill reflects what landed on the SD card.
Fusion 360 Design Files¶
You can attach the original .f3d design source alongside an archive, so the print + design history live together.
| Action | When visible | Description |
|---|---|---|
| Upload F3D | No F3D attached | Pick a .f3d file from your device — stored on disk next to the archive |
| Replace F3D | F3D exists | Swap the existing file for a newer revision |
| Download F3D | F3D exists | Pull the attached file to your device |
| Remove F3D | F3D exists | Delete the attachment (file is removed from disk) |
Archives with an F3D show a small cyan badge on the card (next to the source-3MF badge if one is also attached). Clicking the badge downloads the file. The context menu also exposes an Open in Fusion 360 entry — currently a placeholder that hands the file to your OS for the default .f3d association.
Tag Management UI¶
Tags live in their own settings page so renames, deletions, and bulk edits don't require digging through individual archives.
Archives → gear icon next to the tag filter dropdown.
| Action | Description |
|---|---|
| Search | Filter the tag list by name |
| Sort | Order by usage count or alphabetically |
| Rename | Update the tag name across every archive that uses it (single bulk operation) |
| Delete | Remove the tag from all archives |
| Colour pick | Assign a colour to the tag — propagates to the chip rendering on cards and filters |
Bulk rename
Renaming a tag rewrites every archive's tag list in one transaction — great for fixing typos (abs→abs) or merging similar tags (gift_box + giftbox → gift-box).
Designer Attribution & External Links¶
Archives originating from MakerWorld carry the designer name + link automatically — see MakerWorld. For everything else (Printables, Thingiverse, custom designs) you can add the metadata by hand:
- Open the archive detail page.
- Click Edit Details.
- Fill Designer (display name) and External Link (any URL).
The card surfaces the designer + a globe button:
| Source | Globe-button behaviour |
|---|---|
| External Link set | Opens the custom URL |
| MakerWorld auto-detected | Opens the auto-extracted MakerWorld URL |
| Neither | Globe button disabled |
Designer attribution is searchable via the archive search box, so "all prints by <designer>" is a single query.
Archive Cards & Actions¶
Each card shows the thumbnail, filename, printer / model line, duration, status badge, filament, tags, and the order badge. The order badge is clickable — it jumps to that order's page (the click doesn't bubble up to open the archive modal).
The printer / model line is uniform across provenance: archives tied to a real BamDude printer used to render H2D-1 GCODE … while slicer-only uploads rendered Sliced for X1C GCODE … — two different shapes on the same line. The Sliced for prefix is gone, so both now read as <name-or-model> [bed-icon] GCODE <hash> and scan identically regardless of whether the archive came from a live printer or a slicer-only upload.
The build-plate icon sits next to the printer / model name and reflects the plate the 3MF was sliced for (Cool / Cool SuperTack / Engineering / High Temp / Textured PEI / Smooth PEI), with the full plate name in the hover tooltip — so you don't have to open the source 3MF in a slicer just to read the bed setting before a re-print. The icon is populated from curr_bed_type in the 3MF's slice_info.config (per-plate, authoritative) with a fallback to project_settings.config for older 3MF shapes; archives created before this feature shipped show no icon until you hit the per-archive Rescan action, which re-parses the on-disk 3MF.
| Button | Description |
|---|---|
| Reprint | Print immediately on a connected printer. Creates a new archive row but keeps the same source_content_hash, so reprint history stays linked. |
| Schedule | Add to the print queue. |
| Open the 3D preview. | |
Download the 3MF file. Disabled if file_path is empty. |
|
| Edit archive details (tags, notes, order and line, cost, photos). | |
Retry 3MF download. Only visible when file_path = "". |
An archive can be filed under an order — or counted into stock
The edit dialog carries an Order and a Line picker, and the Archives page's selection mode offers Assign to order for a whole batch — which is how a print started from the printer's own screen reaches the order it belongs to. A print that finished successfully and is filed under no order also offers Count into stock, putting its good parts onto the product's shelf; a failed or cancelled print made nothing to count. See Filing a print under its order and Free stock of parts.
Action button labels hide on narrow card widths
Reprint / Schedule / Slice labels appear only at viewport ≥ 1280 px where the responsive grid (md:2 lg:3 xl:4) gives cards real horizontal room. Below that the buttons render icon-only and the existing title= attribute serves as the hover tooltip — fixes the previous "Re..." / "Sc..." label-truncation on narrow viewports.
View Modes¶
- Grid — large thumbnails for visual browsing.
- List — compact table for data-focused browsing; one row per archive with sortable columns and inline edit / delete.
- Calendar — month-grid view of archives by date. Each day cell shows a count badge + colour coding (success / failure / mixed). Clicking a day filters the grid view to that day's prints.
Cross-view highlighting¶
Hover an archive in the List or Calendar view and the matching cell / row highlights in the other; clicking jumps directly. Clicking an archive in the calendar also auto-switches to grid view, scrolls to the selected card, and highlights it with a yellow border for 5 seconds — useful for finding a specific print across views.
Tags & Filtering¶
Organize archives with custom tags. Filter by printer, tags, material, color, file type, favorites, and the duplicates view (which uses COALESCE(source_content_hash, content_hash)). Tag management is under the gear icon next to the tag filter.
Sorting¶
In List view, click a column header — Name, Printer, Date or Size. Click it again to reverse. Each column opens at the end worth looking at first: the newest print, the biggest file, names A-Z.
Sorting by printer uses the name shown in the column, so a print from a printer you have since deleted keeps its place in the order rather than disappearing or collecting at one end.
Print time has no sorting. The archive has never been able to order by it, and a header that looked clickable but did nothing would be worse than a plain one.
Grid view has no headers to click, so it keeps the sort dropdown beside the colour filter.
Paging¶
How many archives per page, which page you are on, and how many there are in total are one control at the end of the list — the same bar the spool table uses.
It stays put when everything fits on one page: the arrows go, the size selector remains. Otherwise "24 of 24" would be a dead end, with no control left to ask for more.
Changing the page size returns you to the first page — staying on page 3 of a re-sized list lands somewhere you did not ask for, and past the end when the list gets shorter.
Filter chips¶
| Chip | Behaviour |
|---|---|
| Printer | Single-select; defaults to "All printers". |
| Colour | Multi-select. Default semantics is OR — pick Red + Blue to see archives that used Red or Blue. The chip strip exposes an AND/OR toggle — flip to AND when you need archives that used Red and Blue together (multi-colour prints). |
| Material | Multi-select OR (PLA + PETG → either). |
| Kind | Single-select: All prints / Calibration prints / Regular prints. Keyed on the is_calibration flag the Filament Calibration wizard stamps on every test print it dispatches (since 0.4.5). The old GCODE / SOURCE / ALL split lost meaning once archive went print-history-only in 0.4.2. |
| Favourites | Toggle: show only archives flagged with the heart. |
| Date range | Two-input picker: shows archives whose started_at falls inside the range. Pairs with the calendar view. |
| Status | Multi-select status filter — printing / completed / failed / cancelled / archived. |
| Duplicates | Toggle: groups rows by effective_hash so multi-printer reprints collapse into one card with a count badge. |
Batch operations
Enter selection mode to tag, assign an order, or compare multiple archives at once.
Quick search
Press / to jump to the search box from anywhere on the page.
See Also¶
- Print Queue — how queue items become archives, batch tracking, and the post-m019 archive ↔ queue stats refactor.
- File Manager — the library side of the link, including per-file
print_countandlast_printed_at. - Swap Mode — swap macro events and
execute_swap_macrosflags carried inextra_data. - Orders, Products & Stock — filing a print under an order, and counting an order-less print into free stock.
Originally based on Bambuddy documentation; substantially rewritten for BamDude 0.4.x.