X3DStudios

Automating Prototype Iterations With CI for Hardware

X3D Studios··9 min

Continuous integration for hardware means running a physical revision through the same gates you run code through: build the mesh from parametric source, lint its geometry, price it, and only then order the ones that passed. Nothing about that requires a printer on your desk — each stage is an HTTP call, so it fits inside the CI job you already run on every commit. The difference from software CI is that the last stage costs real money, which is exactly why the gates in front of it matter.

What CI Actually Means for a Physical Part

The value of CI was never the automation itself. It was that failures became cheap and early. A hardware loop has the same failure modes, just slower and more expensive: a mesh with a hole in it wastes six hours of machine time before anyone notices, and a units mistake turns a 40 mm bracket into a 400 mm one that will not fit on a plate. Map each software stage onto its physical equivalent and the pipeline writes itself.

Software CI stageHardware equivalentWhat it costs to skip
BuildExport a mesh from parametric CAD sourceNobody can reproduce last week's part
LintGeometry check: watertight, manifold, wound consistentlyA slicer silently patches the hole its own way
Unit testAssert dimensions and minimum feature sizeThe part prints and then does not fit
Budget checkQuote the revision in grams and dollarsA scale mistake shows up on the invoice
DeploySubmit a print orderManual uploads, manual typos
MonitoringPoll the order status until it shipsNobody knows where revision 7 went
Pipeline diagram: commit CAD source, export mesh, gate one at the printability endpoint, gate two at the price endpoint, then order and track, with failed gates routing back to CAD
Two gates stand between a commit and a printer. Both are just POST requests.

Stage 1: Make the Geometry Reproducible

The precondition for all of this is that the model can be built by a machine. A .f3d file someone exported by hand from their laptop is the hardware equivalent of a binary checked into the repo with no source. Parametric, text-based CAD — OpenSCAD, CadQuery, build123d, or a Fusion 360 script — gives you a diffable source file, meaningful pull requests, and a headless export step.

# build.sh — one command, deterministic output
SHA=$(git rev-parse --short HEAD)
openscad -o "build/bracket-$SHA.stl" \
  -D 'wall=2.4' -D 'bolt_d=5.2' src/bracket.scad

ls -lh "build/bracket-$SHA.stl"

Two rules keep this honest. Name the output after the commit that produced it, so an order six weeks old can be traced back to a diff. And treat the mesh as a build artifact, not a repo file — regenerate it, do not commit it, or you will eventually ship a part that does not match its source.

⚠️Check your exporter's default format. We accept STL, GLB, 3MF and G-code (.gcode or .gcode.3mf). OBJ is not accepted, and finding that out in the last stage of a pipeline wastes the whole run.

Stage 2: Lint the Mesh Before It Costs Anything

This is the gate that pays for itself. Our printability endpoint takes a mesh and returns a pure-geometry report — no account, no API key, no credits, because the question it answers comes before anyone pays for anything. It is a single multipart POST with the file in a field called model.

curl -s -X POST https://x3dstudios.com/api/printability \
  -F "model=@build/bracket-$SHA.stl" > report.json

jq -e '.report.verdict != "error"' report.json || {
  echo "geometry gate failed:"; jq -r '.report.issues[].title' report.json; exit 1;
}

The response wraps a flat report object, which makes it easy to assert on. These are the fields worth wiring into a build:

FieldWhat it tells youFail the build?
verdictready, warning or error — the one-line summaryYes on error
watertightWhether the surface is closedYes
holes / largestHoleMmCount of boundary loops and the widest rim in mmYes if largestHoleMm is non-trivial
nonManifoldEdgesEdges shared by three or more facesYes
degenerateFacesZero-area triangles from a bad booleanWarn, then investigate
consistentWindingWhether every normal points the same wayYes
boundingBoxMmOverall size in mm — assert your expected dimensions hereYes on drift
smallestExtentMmSmallest overall dimension, a proxy for thin partsWarn below ~1 mm
triangleCount / vertexCountMesh density after weldingWarn on runaway tessellation
unitsAssumedTrue when the file did not declare its scaleYes for GLB exports
⚠️unitsAssumed is the one people ignore and regret. A GLB can be authored in any scale, so getting it wrong is a factor of 1000 — a 40 mm bracket arriving as a 40 mm-tall speck or a part that will never fit a plate. Assert on boundingBoxMm every run.

One caveat worth writing into your assertions: smallestExtentMm is the smallest overall dimension of the part, not its minimum wall thickness. Real wall thickness needs a distance field and this check does not compute one, so treat it as a smoke alarm rather than a measurement. /blog/why-print-ready-matters-mesh-validation goes through what each of these defects actually does to a slicer.

Stage 3: The Budget Gate

A geometry check tells you the part is printable. It does not tell you whether you meant to spend that. Our quote endpoint takes a bounding box and returns weight, hours and price, with no auth required, which makes it a natural threshold to assert against.

curl -s -X POST https://x3dstudios.com/api/price \
  -H "Content-Type: application/json" \
  -d '{"bboxMm":{"x":90,"y":60,"z":30},"material":"pla","quality":"standard","quantity":1}'

# { "grams": 90, "printHours": 4.46, "materialCost": 10.8, "machineCost": 0,
#   "perUnit": 10.8, "subtotal": 10.8, "shipping": 7, "total": 17.8, "currency": "USD" }

PLA is $0.12 per gram all-in — filament and normal machine time together — with a $1.00 minimum per part and billable weight rounded up to the whole gram. Shipping is a flat $7 in the US, free over $50. The endpoint also enforces the plate: anything that will not fit in 340 × 320 × 340 mm in its best orientation comes back as a 400, which is a free check that your revision still fits the fleet.

Bar chart of quoted PLA cost for one bracket at four scales: 60x40x20mm is 27g at $3.24, 90x60x30mm is 90g at $10.80, 120x80x40mm is 213g at $25.56 and 150x100x50mm is 416g at $49.92, the last two crossing a $25 budget gate
Volume is cubic. A doubled dimension is roughly eight times the material.

That cubic relationship is the reason the budget gate catches things the geometry gate cannot. A parameter that accidentally scaled by 2 still produces a perfectly watertight, perfectly manifold mesh — it just costs eight times as much. A threshold of "fail if total exceeds $25" turns that into a red build instead of a surprise on a statement.

ℹ️The quote endpoint estimates from a bounding box. When you actually order, we slice the mesh in OrcaSlicer where we can, and the measured number wins — a thin stencil that estimates at 12.9 g weighs 21 g once sliced, because an estimate cannot see walls. Set your CI threshold with a little headroom.

Stage 4: Order Only What Passed

The deploy step is one authenticated POST with the file attached — the same submission the upload form at /print makes for you. Gate it behind whatever your team gates a production deploy behind — a tag, a manual approval, a label on the pull request — because this is the stage that spends money.

curl -X POST https://x3dstudios.com/api/print/orders \
  -H "Authorization: Bearer $X3D_API_KEY" \
  -F "file=@build/bracket-$SHA.stl" \
  -F "material=pla" -F "quality=standard" -F "quantity=3" \
  -F "recipientName=Hardware Team" \
  -F "street1=1 Analytical Way" \
  -F "city=Austin" -F "state=TX" -F "zip=78750" -F "country=US" \
  -F "note=rev $SHA — check bolt hole clearance"

Quality is standard at 0.2 mm layers, or premium at 0.1 mm for 1.75× the rate — worth it for a surface finish review, wasteful for a fit check. Quantity is where prototyping economics get pleasant: bulk pricing starts at 5 units for 5% off and 10 units for 10% off, so ordering three variants of the same bracket in one run is usually cheaper per part than ordering one and iterating. /blog/add-3d-printing-to-your-app documents every field on this endpoint.

⚠️A 201 does not always mean paid. If the card on file is declined, the order is still created with paid: false and a paymentUrl to finish by hand. Branch on paid, not on the HTTP status code.

Stage 5: Track It Like a Deploy

Store the order code that comes back. It is the only handle you need afterwards, and GET /api/print/orders/:code with the same key returns the current status, the estimated cost and a tracking object once the part is moving. The statuses run in a straight line and never go backwards, which makes them easy to render on a dashboard next to your build.

StatusWhat it means for the revision
PENDING_PAYMENTCreated but unpaid — the card failed, use paymentUrl
RECEIVEDPaid and in review
ACCEPTEDApproved and queued to a printer
PRINTINGOn a machine right now
PRINTEDOff the plate, being packed
SHIPPEDTracking number and carrier are populated
DELIVEREDClosed — go measure the part

Between ACCEPTED and PRINTED the farm runs its own checks that you get for free: every G-code file is validated before a nozzle heats, the fleet carries live camera streams, and the four-tray AMS handles filament swaps without an operator. If you would rather be told than poll, /blog/webhooks-for-atoms works through pushing these transitions into a chat channel.

What to Run When

Running the whole pipeline on every commit is the wrong default — the first two stages are free and fast, the last one is neither. A sensible split:

TriggerStagesCost
Every pushExport + geometry check + dimension assertsFree, seconds
Every pull requestAdd the quote gate and post the price as a commentFree
Merge to mainNothing automatic — hold the artifactFree
Tagged release or manual approvalSubmit the print order, poll to delivered$0.12/g PLA + $7 US shipping

That shape gives you what CI was always for: a broken revision is caught in seconds by a machine, a costly revision is caught before it is ordered, and a human only gets involved to say "yes, print it." Turnaround from that approval is typically 24 to 48 hours out of Austin, which puts a full design-build-measure loop inside a week. /blog/rapid-prototyping-48-hours walks the same loop from the workshop side, and the endpoint reference lives at /api-docs.


FAQ

Can you automate 3D printing from a CI pipeline?

Yes. Export the mesh in your build step, POST it to a geometry check, POST its bounding box to a quote endpoint to assert on cost, then POST the file to the order endpoint with an API key when a human or a tag approves it. All four are plain HTTP calls, so GitHub Actions, GitLab CI or a cron job on a box can all drive them.

How do you test a 3D model automatically?

Assert on the geometry report: watertight true, nonManifoldEdges zero, consistentWinding true, degenerateFaces zero, and a boundingBoxMm that matches the dimensions your design intends. Those five catch almost every export bug. Fit and function still need a physical part, which is what the order stage is for.

What does one prototype revision cost through the API?

PLA is $0.12 per gram all-in with a $1.00 minimum per part, and US shipping is a flat $7. A 90 × 60 × 30 mm bracket quotes at about 90 g, so roughly $10.80 for the part and $17.80 delivered. Three variants in one order share the shipping, and five or more units take 5% off.

Does the pipeline work with OBJ files?

No — configure your exporter for STL, GLB, 3MF or G-code instead. STL and GLB uploads go up to 100 MB and pre-sliced files up to 40 MB. STL is the safe default for a single-body bracket; 3MF is better when the revision has multiple bodies you want to keep separate for colour.

Ready to get started?

Upload a 3D model for instant pricing, or generate one with AI.