← Blog

Can you version control G-code like software?

Git handles the inputs well and the sliced output badly. How a pinned-slicer build regenerates G-code deterministically, and where the print record has to live.

gcodeversioningworkflowautomation

Yes, with one substitution: version the build, not the artifact. Keep the source model and the slicer settings in the repository, pin the slicer itself so the same commit produces the same G-code, and put the record of which slice actually printed well somewhere that understands prints. Git handles the first half well and the last part not at all, which is usually where the idea stalls.

Committing slices directly fails in ways anyone who has tried it can recite. A 20 MB file per export, a diff of thousands of coordinate lines surrounding the handful of header comments you wanted to compare, and binary .bgcode that Git cannot read at all. Tracking G-code revisions works through that comparison in full, so this post treats it as settled and looks at what has changed recently: slicing has become something you can run as a reproducible build step.

Slicing as a build step

G-code felt unversionable for a long time because producing it meant a person clicking through a GUI, against a profile that lived in the slicer’s config directory where nothing else could see it. A build performed by hand is not reproducible, so the output file becomes the only evidence of what happened, and that is exactly the pressure that pushes people to commit the output.

estampo turns that around. Parts, slicer settings, and printer targets go in one estampo.toml, and slicing runs headless through a pinned slicer image in Docker: OrcaSlicer for Bambu Lab machines, CuraEngine for everything else. Because the slicer version is pinned rather than whatever your laptop updated to last week, the same config is meant to produce the same G-code on another machine or a year from now. It reads STL, STEP, and 3MF, bin-packs parts onto the plate, and ships a GitHub Action that slices on push and uploads the result as a build artifact. Installation is pip install estampo. It was at 0.4.4 under Apache 2.0 when I checked on August 15, 2026.

That changes what belongs in the repository. G-code stops being something to preserve and becomes something you regenerate, the way you would not commit a compiled binary when the build is deterministic. What the commit holds is the model and the TOML.

What a reproducible build still cannot tell you

A build system can prove that a commit produces a particular file. It cannot tell you that the file warped on the 0.4 mm nozzle and came off the 0.6 mm machine straight, because that information does not exist until somebody runs the print and looks at the part. estampo does not track it and does not pretend to: there is no outcome, print log, or library anywhere in it.

So the result needs a home that is neither a filename nor a commit message. PrintStash attaches each slice to its source model as a revision and parses the settings the slicer wrote into the file, including the printer and nozzle profile, layer height, wall count, infill, supports, material, temperatures, and the estimated duration and filament use. Every revision carries one outcome: needs_test, known_good, failed, or archived. Exactly one revision per model holds the recommended marker, so the model page answers the reprint question once instead of offering four candidates. Selecting two revisions puts their parsed settings side by side, which finds the change that mattered faster than reading coordinates.

Handing the artifact off from CI

The ingest endpoint, POST /api/v1/ingest/orca, takes an optional source_hash, the sha256 of the source mesh. When it is present, PrintStash uses it as the dedup key in place of the G-code’s own hash, so the slice resolves onto the model that mesh created rather than arriving as its own library entry. The OrcaSlicer post-processing hook cannot use it, because the slicer never tells the script which mesh produced the export. A CI job driven by a config file that names the part already knows.

What that buys is narrower than it first looks. source_hash files the artifact against the right model, and nothing more. The label, the outcome, and the recommended marker come from a separate call, POST /api/v1/models/{model_id}/gcode-revisions, which takes revision_label, revision_status (defaulting to needs_test), and is_recommended. So a CI upload lands the slice in the right place with its metadata parsed and nothing marked as proven, which is the correct default for a file nobody has printed yet.

The verdict still comes from a person holding the finished part. Auto-marking can cover some of it, but only for jobs dispatched from the library itself: a print you started in Mainsail, or history imported from the printer afterwards, does not promote anything. Measured duration and filament come back from Moonraker only, and the other providers are beta and fall back to slicer estimates, which the compatibility matrix breaks down per provider. The API automation guide covers the login flow, since an API key is a login credential rather than a bearer token.

When this is more machinery than the problem

estampo wants Docker, a pinned image, and a config file for parts you might otherwise slice by hand in under a minute. If you model things in a GUI CAD tool and rarely reslice, the TOML is overhead against no benefit. PrintStash is a service to run and back up, and for one printer and twenty models a careful naming convention really is enough; the comparison table in tracking G-code revisions is mostly about where each approach stops scaling.

The combination earns its keep under fairly specific conditions: parts that get resliced repeatedly, more than one machine in play, or code-CAD work where the model is already a text file in the repository. That is also the case where committing G-code hurts most, since the reslices are frequent and each one is another 20 MB.

Neither project covers the other’s half, and neither has integrated with the other. estampo does not mention PrintStash anywhere in its documentation, so chaining them is glue you write yourself: a CI job that uploads its own artifact.

Questions that come up

Can I regenerate an old G-code file from an old commit?

That is the point of pinning the slicer inside a container image. If the commit holds the model and the config, and the config names an exact slicer image rather than “whatever is installed”, then checking out that commit and running the build again should hand you the same file. Without pinning it will not, because slicer releases change defaults, arc fitting, seam placement, and support generation between versions, and your local install updates on its own schedule. This is the piece that a repository of loose STLs and exported G-code cannot give you at any level of discipline.

Does pinning the slicer really produce byte-identical output?

estampo’s claim is identical G-code across machines from a pinned image and locked profiles, and the mechanism is sound: the same engine version and the same settings applied to the same mesh. I have not measured byte equality across platforms myself, so treat it as the project’s claim rather than something verified here. Either way the practical benefit does not depend on the last byte matching. Removing slicer version drift and profile drift eliminates the two things that actually change a print between one month and the next.

How does a CI job attach a slice to the right model instead of creating a new entry?

Send source_hash with the upload, set to the sha256 of the mesh you sliced. Without it, dedup falls back to the hash of the G-code itself, which no existing model matches, so you get a fresh entry every time. A build system can supply it because the config already names the part file, making the hash a line of script. The shipped OrcaSlicer hook cannot, since it only ever receives the exported G-code path and has no way to learn which mesh produced it.

Is estampo a replacement for a file library?

No, and the two do not overlap much. estampo is a build system: it defines parts and slicer settings in TOML, pins the slicer version in Docker, and generates G-code locally or in CI. It has no concept of print outcomes, no history of which file printed well, no shared view across a fleet, and no file browser. Those are library concerns. Running a build system does not remove the need to record results, and running a library does not make your slicing reproducible.

Do I need Bambu hardware to use a pinned-slicer build?

No. estampo selects the engine by target: OrcaSlicer covers Bambu Lab machines, and CuraEngine covers everything else, shipping 643 Cura machine profiles against 35 Orca ones. A Voron, a Prusa, or a Creality running Klipper goes through the CuraEngine path. The engine choice does change which profile names and overrides you write in the TOML, so a config is not portable between the two paths without editing.

Sources

  • estampo and its PyPI listing for the TOML pipeline, the pinned OrcaSlicer and CuraEngine images in Docker, the GitHub Action, the bundled machine profile counts, and version 0.4.4 under Apache 2.0. Checked August 15, 2026.
  • PrintStash core concepts and API reference for the vault model and the authentication flow. The source_hash parameter, the four revision outcomes, and the single-recommendation rule were read from backend/app/api/v1/ingest.py, backend/app/db/models.py, and backend/app/api/v1/models.py in the repository, which documents them in more detail than the reference page does.

For the outcome side on its own, one model, many G-codes covers the status rules in more depth, and version-controlling slicer profiles handles the case where you want the presets themselves in Git rather than a build system driving them.