You know the story: it's late, you slice the model, start the print, go to bed — and wake up to spaghetti, a detached first layer, or a shape that has a hole in it that the slicer silently "fixed" by creating a mess.
Almost every failed print I've had traces back to one of five problems that are detectable before slicing. Here's the checklist I run on every mesh now, with the Python one-liners for each. Total time: about 10 minutes, often less.
All snippets use trimesh (pip install trimesh numpy).
1. Is the mesh watertight?
A mesh with holes will confuse your slicer: it has to guess where the surface is, and some slicers guess badly.
import trimesh
m = trimesh.load("part.stl", force="mesh")
print(m.is_watertight) # False = the slicer is going to improvise
If it's not watertight, don't panic — most holes from bad boolean ops or exports can be closed automatically:
from trimesh.repair import fill_holes
for _ in range(4):
if m.is_watertight: break
fill_holes(m); m.merge_vertices(); m.remove_unreferenced_vertices()
print(m.is_watertight)
2. Are the normals consistent — and pointing outward?
Inconsistent winding makes slicers produce walls of zero thickness. Flipped normals make the slicer think your solid is the void.
from trimesh.repair import fix_winding, fix_normals
fix_winding(m) # make all faces agree on direction
fix_normals(m, multibody=True) # orient normals outward
print(m.volume) # must be POSITIVE now; negative = inside-out mesh
If the volume is negative after this, invert: m.invert().
3. Is the scale sane?
STL is unitless. A model exported in meters prints 1000x too small; one from some CAD tools comes in at 0.001mm and the slicer refuses it or scales it "helpfully".
import numpy as np
bbox = m.bounds[1] - m.bounds[0]
print([round(v, 2) for v in bbox]) # expecting something like [40.0, 25.0, 12.0] mm
Rule of thumb: for typical FDM parts, the largest dimension should be between ~5 and ~300 mm. Outside that range, suspect units.
4. How much overhang are you signing up for?
Only faces that point downward beyond ~45° need support — vertical walls print fine unsupported, and the face resting on the bed obviously doesn't count. A quick, honest estimate:
up = np.array([0, 0, 1.0])
nz = m.face_normals[:, 2] # downward faces have nz < 0
centers_z = m.triangles.mean(axis=1)[:, 2]
bed_z = float(m.bounds[0][2]) + 0.01 # ignore the bed-adjacent face
needs = (nz < -0.707) & (centers_z > bed_z) # down-facing, beyond 45 deg, off the bed
frac = m.area_faces[needs].sum() / m.area_faces.sum()
print(f"{frac:.1%} of the surface needs support")
(The naive version of this check — abs(nz) < 0.707 — wrongly flags every vertical wall of a box as overhang. Walls are fine; it's downward-facing surfaces that cost you filament.)
Above ~3–5%: either rotate the part (try several orientations and pick the smallest fraction) or plan for supports. This single check has saved me the most filament.
5. Degenerate faces and duplicate geometry
Exports from CAD boolean operations often contain zero-area faces and duplicated triangles. They don't always break the print, but they make every downstream tool slower and some slicers choke on them.
m.update_faces(~np.isclose(m.area_faces, 0.0))
m.merge_vertices()
m.remove_unreferenced_vertices()
Wrapping it into one command
Before the paid part: the five checks above are free and open source as stl-precheck — a single-file CLI with a PASS/ATTENTION verdict and JSON output for batch use.
After running these by hand a dozen times, I packaged the whole checklist into a small CLI: Mesh Rescue — one command prints the full diagnostic report, auto-fixes what's safely fixable (holes, winding, duplicates, orientation), and scores your print-bed orientation with a support estimate. It also refuses to output anything when the mesh is too damaged to trust, rather than producing a silently-bad file.
For the step after slicing, G-code Tuner post-processes G-code (z-hop cleanup, fan speed floors, speed caps) with sanity-checked edits — the kind of tweaks that otherwise mean hand-editing 40,000 lines of G-code.
The honest caveats
- Automatic hole-filling works for simple gaps; big structural holes need manual repair.
- The overhang estimate is area-based — it doesn't know about bridges (which are often printable) or tiny cosmetic overhangs (which are fine unsupported).
- Always dry-run the fixed mesh through your slicer before committing to a long print.
Print the checklist, not the spaghetti. If you end up building anything on top of these snippets, I'd love to hear what you added.
Top comments (0)