Metadata-Version: 2.4
Name: printcheck
Version: 0.1.1
Summary: Preflight and slice verification for FDM 3D printing - catch the print that will fail before you spend the filament
Author: Son of a Tech
License: MIT
Project-URL: Homepage, https://sonofatech.com/tools/printcheck.html
Keywords: 3d-printing,slicer,gcode,stl,klipper,preflight
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Manufacturing
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Printing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: trimesh>=3.9
Requires-Dist: numpy>=1.20
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# printcheck

**Catch the print that will fail before you spend the filament.**

Three checks that run in under a second, each one written after a print that
actually failed:

```
printcheck preflight model.stl                    # is this orientation printable?
printcheck verify new.gcode --against known.gcode # did anything drift?
printcheck flatten "0.20mm Standard" --profiles-root ... --series ...
```

MIT licensed. Two dependencies (`trimesh`, `numpy`). No account, no cloud, no
telemetry.

## Why

A 13-hour print that dies at layer 40 costs a day and a spool. Almost every one
of those failures is visible in the file *before* you press print — you just
have to look at the right number, and slicers do not show you any of them.

## `preflight` — orientation problems in the STL

```
$ printcheck preflight cap.stl
cap.stl
  size            5.0 x 227.0 x 140.12 mm
  height/footprint 28.02:1  (140.12 mm tall on 5.0 mm)
  flat underside in mid-air  0.02 cm2
  [FAIL] Height/footprint ratio 28.02:1 exceeds 8.0:1 - this stands 140.12 mm
         tall on a 5.0 mm footprint and is likely to detach and be dragged.
```

That is a real file, downloaded from a model site, sliced without looking. It
was a 5 mm-thin wall standing 140 mm tall. It wobbled, detached, and the nozzle
dragged it around the plate, taking its plate-mate with it. The numbers were on
screen before slicing and went unread.

**Two checks, two different failure modes:**

- **Height ÷ smallest footprint dimension.** Over about 8:1 with no brim, stop
  and re-orient.
- **Flat down-facing area sitting above the plate.** This all has to be bridged,
  and long bridges collapse. Laying the same cap flat was not enough — it landed
  hollow-side-down, resting on a thin rim with **109.4 cm² of underside floating
  3 mm in mid-air**. The slicer dutifully bridged all of it; both attempts died
  at Z 3–4 mm, exactly that layer. One more 180° flip took it to **0.0 cm²** and
  it printed clean, 25 of 25 layers.

It also bins overhangs by angle, because 60–75° from vertical genuinely needs
support and it is useful to know that before you assume otherwise.

## `verify` — drift in the gcode

The premise: **you already own a better specification than any documentation —
the gcode from a print that came out right.** Diff against it.

```
$ printcheck verify new.gcode --against known-good.gcode
config: 2 of 565 keys differ from known-good.gcode
  enable_support
      known-good: 0
      candidate:  1
per-object Z range (plate max Z hides a badly oriented part):
  poop_chute.stl_id_0_copy_0            0.20 ->  162.60 mm
support vs overhang: 98% of overhang moves fall in Z bands that also contain
support (peak overhang band 80-100 mm has support).
```

Three things it checks:

**Every config key.** All 565 matching is far stronger evidence than
spot-checking a handful of values you happened to think of.

**Per-object Z range.** Plate max Z is dominated by the tallest object, so a
part lying in completely the wrong orientation is invisible there — a standing
140 mm cap and a flat 5 mm cap produced identical plate max Z. Tracked per
object it is unmissable: 140.20 before the fix, 5.00 after.

**Support against overhang, by height.** Counting `Overhang wall` proves
nothing — that classifies a perimeter, it does not say whether anything is
underneath it. The count stayed at 1439 with supports both on and off, which led
to the wrong conclusion once. Comparing the *height distribution* of support
moves against overhang moves does answer it.

**Whether the supports have an interface at all.** A 13-hour print started
dropping layers at a corner with every other check passing. Its profile set
`support_interface_top_layers = 2` — and the gcode contained **zero**
`Support interface` moves. The slicer had simply not generated any, so the
near-horizontal undersides landed straight onto a 2.5 mm-spaced lattice across a
0.2 mm gap and sagged into every gap. The setting was right there in the config
block the whole time; only counting the emitted feature catches this.

**Whether support keeps pace as the part grows.** Support that stays flat while
overhang doubles is fine at the bottom and starving by the middle. On that same
print it reported `4.0:1 in the 80-100 mm band` — which is exactly where the
droop was visible on the real part.

## `flatten` — the silent one

QIDI Studio, and the Orca/Bambu Studio family it forks, **does not resolve
`inherits` when you pass a preset to the CLI as a file path.** It falls back to
built-in defaults for every key the file does not literally contain — and still
reports `Success`.

One real slice came out with volumetric speed 2 instead of 24.5, acceleration
1000 instead of 20000, travel 120 instead of 500, nozzle 200 instead of 210:
**16 of 34 critical settings wrong**, print time inflated 2.9×, and
`filament_usage_g` reported as 0, which is the tell-tale sign.

`flatten` walks the chain through the vendor index and merges it into one
self-contained file. It goes through the index rather than guessing filenames
because a preset's name and its filename often differ — `QIDI PLA Rapido@X4`
lives in `QIDI PLA Rapido.json`, and guessing fails on exactly the profiles that
matter. It also warns when a flattened process preset has no `curr_bed_type`,
which silently defaults the bed to Cool Plate at 35 °C.

## Install

```
pip install printcheck-0.1.0-py3-none-any.whl
```

or from source:

```
pip install .
```

## Exit codes

`0` clean, `1` at least one `FAIL`. Wire it into a build script and it will stop
a bad plate before it reaches the printer.

## Tests

```
pytest
```

The thresholds came from one real part printed in three orientations with three
known outcomes. That model belongs to its designer and is not redistributable,
so the shipped fixtures are synthetic parts reproducing the same geometry
classes. If you own the same model, point `PRINTCHECK_FIXTURES` at it and the
real regression tests run too.

## Scope

Written against QIDI Studio and Klipper, but only `flatten` is vendor-specific.
`preflight` works on any STL, and `verify` works with any slicer that emits a
`; key = value` config block — PrusaSlicer, OrcaSlicer, Bambu Studio and their
forks all do.

Reports what it measures. It will not re-orient your model for you.
