Get reproducible prints across a fleet
Deterministic slicing makes the file identical, not the machines. What a pinned build pins across a fleet, and where the machine-to-slice record lives.
Two different problems share the word reproducible, and a fleet makes you solve both.
The first is deterministic slicing: the same model and the same settings should produce the same G-code today, next year, and on a colleague’s laptop. That one has an answer. Pin the slicer inside a container image, keep the profiles and the part list in the repository, and the file stops drifting on its own.
The second is a reproducible print: the file that came off machine one clean should come off machine four clean. No build system can promise that, because the variance that ruins the part lives in nozzles, flow calibration, and filament rather than in software. What determinism buys you on a fleet is narrower and still worth having: when the same file fails on one machine, you know the file is not the reason.
The half that pins cleanly
estampo is the tool built for the first half. Parts, slicer settings, and printer targets go in an estampo.toml, slicing runs headless through a pinned slicer image (OrcaSlicer for Bambu Lab targets, CuraEngine otherwise), and the pinned version is the point: the config names 2.3.1 rather than whatever your laptop updated to last week. It was at 0.4.4 under Apache 2.0 when I checked on August 17, 2026, and the docs are direct about where it stops. It is printer-agnostic and produces files, with no one-click printing and nothing that talks to a machine.
If you would rather keep slicing by hand, the drift you care about is in the presets, and Git on the slicer’s user configuration folder covers it. Version-controlling slicer profiles has that setup, and versioning G-code like software covers the build-system route in more depth than this post needs.
Either way you end up in the same position: the input is under control, and every remaining difference is downstream of the file.
Where a fleet drifts anyway
The assumption that breaks first is that a slice is machine-neutral. It never was. Every file carries the machine preset’s start and end G-code, the bed size it was arranged for, and whatever macro names that preset expects. Send a file sliced for a Klipper printer whose START_PRINT takes a bed temperature parameter to a machine that defines the macro without it, and the job either aborts on the first meaningful line or skips a step quietly, which is worse.
Suppose you have fixed that and the fleet really does share one machine preset. Plenty still moves. A 0.4 mm nozzle with four hundred hours on it is not the 0.4 mm nozzle the profile was tuned against, and neither is a different brand’s 0.4 mm. Rotation distance, flow multiplier, and pressure advance live in each printer’s own configuration rather than in the G-code, so two machines running your identical file are extruding slightly different amounts of plastic. Bed mesh, Z offset, and plate surface are also per machine, and between them they decide adhesion before the file has done anything interesting. Then there is the filament: a spool left open for a month does not behave like a fresh one, and an enclosed machine and an open frame in a cold garage are not printing the same PETG.
That is an argument for pinning the build, not against it. Determinism gives you an identical input to a process that is not identical, and the benefit is diagnostic. A failure on one machine becomes a question about that machine, because the file has already been ruled out.
One slice per machine class, not one file for the fleet
The practical shape that follows is to slice per machine class and keep the classes explicit. Machines that share a nozzle size, a build volume, and a macro set can share a file. Anything else gets its own slice.
PrintStash reads the settings out of each upload and stores them against the revision, including the slicer name and version, the printer preset, nozzle diameter, layer height, wall count, infill, supports, temperatures, and the estimated time and filament use. The first time a file names a printer preset, it also creates a local printer profile from it, recording the model, slicer, and nozzle diameter. That gives you something to filter on later: every revision sliced for the 0.6 mm machines, or every revision that came out of a slicer version you have since replaced.
The slicer version is the field worth watching on a fleet. If half your revisions were sliced with OrcaSlicer 2.2 and half with 2.3, the record says so, and a known-good verdict from before the upgrade is a weaker claim than one from after it.
What the record holds, and what it will not tell you
Each print job records which printer ran it, which revision, the state it ended in, and any error. A job can also name a printer that PrintStash does not manage, which covers the machine that is not connected yet. On Moonraker, the measured filament and duration come back with it, and the cost is frozen at completion. The other providers are beta and report less; the compatibility matrix is the per-provider breakdown.
The outcome, though, is one field on the revision, not one field per printer. A revision is needs_test, known_good, failed, or archived, and known_good means the slice printed successfully somewhere. It does not mean it printed successfully on the machine you are about to send it to. The recommended marker has the same limit in a stricter form: a model holds exactly one recommended G-code revision, enforced in the database, so there is no per-printer recommendation to set.
For a fleet of matched machines that hardly matters. For a mixed one it decides how you label things. Put the machine class in the revision label, keep one revision per class, and let the job history carry the per-machine detail, since it is the only record that knows which printer ran what.
One default deserves a look before you trust the verdicts. Auto-marking known good on a successful print is on out of the box, and it promotes a revision that was untested or unset as soon as any connected printer finishes a job with it. It never overwrites a failed or archived verdict you set by hand, and it does not check which machine ran the job. On a fleet of identical machines that is fine. On a mixed one, either turn it off in the settings or read known_good as the weaker claim it is making.
Dispatch does not check compatibility either
Least-busy assignment scores by how much work is in front of each machine and nothing else, so routing will not steer a 0.4 mm slice away from the machine wearing a 0.6 mm nozzle. Since v0.12.0 a preflight does compare the file’s material and nozzle metadata against the target before a direct send and flags a mismatch, which catches the worst version of this at the last moment; the override is audited rather than silent. Matching the job to the machine up front is still the operator’s call, which is another argument for one slice per class and labels you can read at a glance. Sending G-code to multiple printers covers what the queue does do, including maintenance windows and per-printer access.
If a CI job is producing the slices, upload them with source_hash set to the sha256 of the source mesh. Dedup then resolves the artifact onto the model that mesh created instead of filing it as a new library entry, and the revision arrives as needs_test, which is the right state for a file no machine has run yet.
What this looks like when something goes wrong
A fleet with a pinned build and per-machine job history still will not print identical parts. What it does is shorten the argument. Machine four starts producing warped corners, and you can see that the file has not changed since the last good run, that machines one and two printed the same revision last week without trouble, and that the only recent event on four was a nozzle swap. Compare that with reconstructing the same picture from four people’s exported files and a folder of names ending in _FINAL.
Questions that come up
Is the same G-code file safe to run on two printers of the same model?
Usually, and it is the only case where sharing a file is comfortable. Two machines of the same model share a bed size, a kinematic setup, and, if you built them from the same image, the same macro names, so the start G-code in the slice means the same thing on both. What they do not share is calibration. Rotation distance, pressure advance, Z offset, and bed mesh live in each printer’s own configuration, and nozzle wear diverges from the day you install them. Expect the same file to work on both and expect the first layer to be the thing that differs. Printers of different models should get their own slice, since the machine preset baked into the file is model-specific.
Does PrintStash record known-good per printer?
No. The outcome is a single field on the G-code revision, so known_good records that the slice printed successfully, without saying which machine proved it. The per-machine detail is in the print job history instead, since every job stores its printer, its revision, and the state it ended in. On a mixed fleet the workable pattern is one revision per machine class with the class in the label, for example 0.6 nozzle, farm rack next to 0.4 nozzle, enclosed. Both can be known good at once. Only one revision per model can hold the recommended marker, so give it to the slice you would reprint today and rely on labels for the rest.
Should auto-marking known good stay on for a fleet?
Turn it off if your machines are not interchangeable. The setting promotes a revision to known_good after a successful print from a connected printer, and it does not record or check which printer that was, so on a mixed fleet it produces verdicts that read stronger than the evidence behind them. It will not overwrite a failed or archived status you set yourself, so manual verdicts survive either way. With identical machines the automation is worth keeping, because the alternative is remembering to set a status while you are holding the part.
How do I tell which slicer version produced a revision?
It is parsed out of the file header on upload and stored with the revision, along with the printer preset and nozzle diameter, so you can read it off the revision months later. This matters more on a fleet than on one printer, because slicer upgrades tend to happen on whichever workstation someone was using, and arc fitting, seam placement, and support generation change between releases. A pinned slicer image in the build removes the problem at the source. The parsed field is how you audit the slices you made before you pinned anything.
Can the queue route a job to a printer that can actually run it?
Not by capability. Routing offers manual selection, a default printer, or least-busy assignment, and least-busy counts queued work rather than checking nozzle diameter, loaded material, or plate type. So the fleet queue spreads load, and you decide what is safe to spread. In practice that means keeping the machine class visible in the revision label and picking the target by hand when the fleet is heterogeneous, or reserving least-busy routing for a group of machines that really are interchangeable.
Sources
- estampo and its PyPI listing for the pinned OrcaSlicer and CuraEngine images, the TOML pipeline, version 0.4.4 under Apache 2.0, and the project’s own statement that it is printer-agnostic and stops at the sliced file. Checked August 17, 2026.
- PrintStash core concepts and the printers guide for revisions, outcomes, and job history. The four revision outcomes, the single-recommendation database constraint, the parsed slicer name and version, the local printer profiles created from parsed presets, and the printer-agnostic behavior of auto-marking known good were read from
backend/app/db/models.py,backend/app/services/print_results.py,backend/app/services/profile_detection.py, andbackend/app/services/runtime_config.pyin the repository.
For the single-printer version of the same workflow, tracking G-code revisions is the full walkthrough, and one model, many G-codes covers the outcome states on their own.