X3DStudios

Upload and print

Upload an STL, GLB, 3MF or sliced G-code at /print, get a quote weighed from a real slice, then order the part printed in Austin and shipped.

The /print page takes your own file, validates it, weighs it with a real slice, and turns it into an order we print in Austin, Texas and ship to you. You choose material, colour, quality, quantity, scale and infill; the price comes from the grams the slicer says will leave the nozzle. You need an account, because the file is stored against it and the order is billed to it.

What you can upload#

KindExtensionsSize limitWhat we read from it
Mesh.stl100MBBounding box, volume, triangle count
Mesh.glb100MBBounding box, volume, triangle count
Sliced.gcode40MBPrint footprint, filament grams, print time, plate preview
Sliced.3mf, .gcode.3mf40MBPrint footprint, filament grams, print time, plate preview
Sliced files are capped lower on purpose: validating one costs several times its size in server memory, because the file is read as text and a 3MF's inner G-code inflates on top of that.
OBJ is not accepted
The uploader rejects OBJ. Convert it to STL and upload that. The paste-a-link importer can fetch and preview OBJ, GLTF and FBX from MakerWorld, Thingiverse, Printables, Cults3D, MyMiniFactory and Thangs, but it says on screen that it cannot print those directly.

A mesh must fit the plate: 340 × 320 × 340 mm on a Bambu Lab H2S. The three dimensions are sorted before they are compared, so a part that only fits when rotated still passes. An already-sliced file skips this check entirely — your slicer already arranged it on a plate, and the footprint it reports can include purge lines and prime towers that are not part of the model.

How the upload works#

The bytes go from your browser straight to storage, not through the app. There is a 32 MiB cap on request bodies in front of the service, and anything over it is rejected before the app ever sees it — so a 100MB model could never arrive as an ordinary POST. It used to fail as a confusing validation error rather than a size error. Now the app hands the browser a signed URL, the browser uploads to storage, and the app validates the stored object afterwards.

  1. 1
    The page asks for an upload URL

    It sends the filename and the size. Format and size are checked here, before a single byte moves — an unsupported extension or an oversize file is refused now rather than after the transfer.

    json
    { "fileName": "bracket.stl", "size": 48213004 }
  2. 2
    Your browser uploads to storage

    A signed PUT URL comes back, valid for 15 minutes, pointing at a staging path. The transfer is browser to storage, so its speed is your connection, not ours.

  3. 3
    We validate what actually landed

    The app measures the stored object itself rather than trusting the size the browser claimed, parses the bytes that actually landed, and only then copies the exact bytes it validated out of staging into their final path. That copy is the point of the staging step: the signed URL stays writable for its full 15 minutes, so validating and printing from the same writable object would let a small part be priced and a large one printed.

  4. 4
    The model is weighed

    As soon as the file is stored, a slice is queued in the background. Until it lands the page shows Calculating, because showing an estimate and then quietly replacing it reads as the site changing its mind about the price.

POST/api/print-upload/initSigned in

Reserve a staging path and return a 15-minute signed upload URL.

POST/api/print-upload/completeSigned in

Validate the uploaded object, store it, and return its measurements.

Upload limit
40 uploads per hour per account. The budget is shared across every leg of the flow, so it cannot be sidestepped by choosing a different transport. Over it you get: Too many uploads — try again shortly.

What the quote is computed from#

Weight. One all-in rate per gram covers filament and normal machine time together, so a 21 g PLA part costs exactly 21 × $0.12 = $2.52 and nothing else is added except postage.

MaterialRateDensity
PLA$0.12/g1.26 g/cm³
PETG$0.12/g1.27 g/cm³
ABS$0.12/g1.04 g/cm³
ASA$0.14/g1.07 g/cm³
Flexible TPU$0.13/g1.21 g/cm³
All-in per-gram rates. Premium 0.1mm layers multiply the rate by 1.75; standard 0.2mm is 1×.
  • Billable weight rounds up to the whole gram, like postage — so the figure agrees with the whole grams Bambu Studio shows you.
  • There is a $1.00 minimum per part.
  • A colour outside the twelve stock spools is priced at $0.20/g, whatever the material, because the spool has to be bought in for your order and most of it will go unused.
  • Ordering several copies of one part discounts it: 5% off at 5 or more, 10% off at 10 or more.
  • Shipping is $7 flat in the US. Checking out through the cart makes it free over $50, and prices international postage at $30 flat, free over $500; Print & Ship always adds the $7.

The grams come from a real OrcaSlicer slice wherever one is possible, not from a volume estimate. The estimate reads a part as 30% solid walls plus infill, which undercounts anything thin badly — a stencil that weighed 21 g on the plate estimated at 12.9 g. That is why checkout refuses to sell an unmeasured print: it would sell below the advertised rate. An already-sliced upload is priced from the filament figure in its own header rather than from that estimate — but checkout still wants a slice of ours on file before it will sell any print, and a bare .gcode is the one thing we cannot re-slice, so send a plain .gcode plate through the print order API (/docs/api/submit-order) rather than this page.

Rough price for a bounding box, before you upload anything
curl -s https://x3dstudios.com/api/price \
  -H "Content-Type: application/json" \
  -d '{"bboxMm":{"x":120,"y":80,"z":60},"material":"pla","quality":"standard","quantity":1,"color":"Black"}'
Response
{
  "filamentChanges": 0,
  "multiColorGrams": 0,
  "multiColorHours": 0,
  "grams": 320,
  "printHours": 15.84,
  "materialCost": 38.4,
  "machineCost": 0,
  "shipping": 7,
  "baseFee": 0,
  "perUnit": 38.4,
  "subtotal": 38.4,
  "total": 45.4,
  "currency": "USD",
  "material": "pla",
  "quality": "standard",
  "quantity": 1,
  "color": "Black",
  "customColor": false,
  "pricePerGram": 0.12
}
That endpoint is an upper bound, not your price
/api/price is given only a bounding box, so it fills that whole box with a part read as 30% solid walls plus 20% infill. A real model is mostly air. Upload the file to get the sliced weight, which is what you are actually charged.

The options you choose#

Print options on /print
materialstringoptionaldefault pla
The plastic. Changes the per-gram rate and the density used to turn volume into weight.plapetgabsasatpu
colorstringoptionaldefault Black
One of the twelve stock swatches, or a hex or name you type beside the custom swatch. Off-palette prices at $0.20/g, and the page says so where you pick it rather than at checkout.
qualitystringoptionaldefault standard
Standard is 0.2mm layers at 1×. Premium is 0.1mm layers at 1.75× — finer surface, roughly twice the machine time.standardpremium
quantityintegeroptionaldefault 1
1 to 20 per order line. Bulk factors apply at 5 and at 10.
scalePercentintegeroptionaldefault 100
50% to 300%, in steps of 5. Weight scales with the cube of it, so 200% is roughly eight times the material. The scaled part still has to fit 340mm.
infillintegeroptionaldefault 20
10% to 100%, in steps of 5. Higher infill means a stronger, heavier, more expensive part. Above 35% the slice — run at the profile's sparse default — stops being representative, and the quote falls back to the volume estimate, which approaches solid volume exactly where the slice diverges.
notestringoptional
Free text kept with the order and shown to the operator on the work order: orientation preferences, colour matching, anything a human should see. Trimmed to 900 characters.
Sliced uploads ignore scale and infill
A .gcode or .gcode.3mf file is the print spec. It is ordered at 100% scale and priced from the filament it says it uses; the scale and infill controls do not apply to it. To change either, re-slice and upload again.

Placing the order#

Print & Ship goes straight to Stripe checkout for that one file. Add to Cart holds it so you can order several files together, and the cart re-prices every print server-side before checkout — the number stored in your browser is never trusted. Either way you get a confirmation email, the order appears on /orders, and it moves through placed, printed, packed, shipped, delivered.

Troubleshooting#

What you seeWhat it meansWhat to do
Unsupported file type. Use STL, GLB, 3MF, or G-code.The extension is not one we print. OBJ is the common case.Convert it to STL and upload that.
File too large (max 100MB for STL files).Over the ceiling for that format — 100MB for meshes, 40MB for sliced files.Decimate the mesh, or for a sliced file re-export with fewer objects on the plate.
This model is 412×180×95mm — larger than the 340×320×340mm build volume.It does not fit in any orientation.Scale it down, or split it and order the pieces.
We couldn't read a printable mesh from that file. Make sure it's a valid, watertight STL/GLB.The parser could not get triangles and a bounding box out of it.Re-export from your CAD tool, and run the free check at /docs/studio/printability first.
We couldn't read the print footprint/filament from that sliced file.The G-code header carries no usable filament or footprint numbers. A 3MF saved as a project rather than exported as a sliced plate lands here.In Bambu Studio or OrcaSlicer, slice the plate and use Export plate sliced file.
Couldn't upload that file to storage. Check your connection and try again.The direct browser-to-storage transfer failed part way, or the signed URL had already expired.Select the file again to get a fresh 15-minute link.
That upload reference isn't valid.The staging path or its token did not match your account.Choose the file again rather than retrying the same reference.
Too many uploads — try again shortly.You are over 40 uploads in the last hour.Wait for the window to roll over. The response carries Retry-After.
We're still measuring this model.Checkout was reached before the slice finished. We do not sell a print we have not weighed.Give it up to a minute and try again. A bare .gcode never clears this, because there is nothing left for us to slice — order that plate through the print order API instead.
Sign in to order a print.Uploads are tied to an account.Sign in, then select the file again.