BMesh Gear
A 14-tooth gear built entirely with bmesh — profile ring, face, extrude — with bm.free() in a try/finally, exactly as the ownership contract demands.
examples/soccer-ball-goldberg/
A soccer ball as a Goldberg polyhedron: a bmesh icosphere truncated at 1/3 per edge, faces ordered by link-topology walks, panels bound by face vertex count.
Rendered headless by the example itself. Select it to enlarge.
runs on 4.5 LTS · 5.1 · 5.2 LTS
blender --background --python examples/soccer-ball-goldberg/soccer_ball_goldberg.py --
falsify it Breaks the contract on purpose and must exit 13
blender --background --python examples/soccer-ball-goldberg/soccer_ball_goldberg.py -- --invert-bind
A runnable example that builds a soccer ball as a Goldberg polyhedron: a bmesh icosphere truncated at exactly 1/3 of every edge, yielding the truncated icosahedron — 12 pentagons, 20 hexagons — with every face ordered by walking the source mesh's own link topology (fan walk per vertex, loop order per triangle). Nothing is hand-listed, following mesh-editing-and-bmesh and the always-free-bmesh rule.
Sibling, not twin: bmesh-gear witnesses parametric extrusion ownership (counts from construction parameters, watertightness). This witnesses polyhedral topology invariants — the truncated icosahedron is an Archimedean solid, so its counts, vertex degree, edge uniformity, face planarity, and circumsphere are all closed forms — plus per-face-class material binding driven by face vertex count, never by enumeration order.
What it witnesses: truncation at 1/3 makes every new edge the same length (a/3), which is what puts all 60 vertices on one sphere. The check asserts, with tolerances printed on success:
V−E+F==2 — the sphere topology. Catches a leaked or dropped face/edge/vertex (probe: one face deleted → exit 3, topology (60, 90, 31)).0.000e+00, tol 1e-6) and every vertex equidistant from it: max deviation 8.956e-06 (exit 10/11).len(poly.vertices), and the check re-derives the expectation the same way — so an enumeration-order shortcut fails (probe: last-12-faces-black → exit 13, 24 misbound faces; inverted classes → exit 13, 32 misbound). Enumeration order coinciding with class order is a construction artifact, not a contract — the check exists for the cases where it doesn't.Tolerance basis: mesh coordinates are float32 and create_icosphere's own trig lands the invariants at a deterministic ~9e-6 noise floor (measured values are byte-identical on 4.5.11 and 5.1.2). The 3e-5 gates sit ~3x above that floor; any genuine contract break is orders of magnitude larger.
Version witness: check output is byte-identical on Blender 4.5.11 LTS and 5.1.2 — same counts, same deviations. One authoring hazard surfaced and is noted in the code: Object.to_mesh_clear() takes no argument on current Blender (passing the mesh raises TypeError) — see depsgraph-and-evaluated-data.
The render is the proof: the faceted Goldberg cage is smoothed by an unapplied Subsurf modifier (panel materials carry through Catmull-Clark per face class), and the ball is grounded by its depsgraph-evaluated lowest vertex. Invert the panel binding and the still inverts with it: white pentagons on a black ball, wrong on sight.
None of this touches the checked mesh, its binding, or any check.
gallery_framing.check_framing before writing the still and returns its exit 10 directly (10 is reserved for this gate; the centroid check uses 14). The larger, rounder ball touched the top edge at the old 55 mm lens; the lens is now 50 mm, and a TRACK_TO aim sits 0.25 below the ball's centre.The floor is dark mown turf rather than the near-black studio floor: a ball alone on a black stage read as an object in a void, and the pitch is the context that makes it a match ball. The back wall and warm wedge are the default stage; measured stage luminance 0.217 sits inside the calibration set.
# Cheap correctness check (no render) — the CI check:
blender --background --python soccer_ball_goldberg.py --
# Falsifier: swap pentagon/hexagon slots. Must exit non-zero.
blender --background --python soccer_ball_goldberg.py -- --invert-bind
# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
blender --background --python soccer_ball_goldberg.py -- --output ball.png
blender --background --python soccer_ball_goldberg.py -- --output ball.png --engine cycles
Per-script sequential checks. 9 is a valid check code; there is no rule against it.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Uncaught exception (FATAL wrapper) |
| 2 | argparse / usage |
| 3 | Topology ≠ 60/90/32 |
| 4 | Euler characteristic ≠ 2 |
| 5 | Face census ≠ 12 pentagons + 20 hexagons |
| 6 | Vertex degree not uniform 3; also --output produced no file |
| 7 | Non-manifold edges |
| 8 | Edge lengths not uniform |
| 9 | Face planarity off |
| 10 | Framing gate (--output path only; gallery_framing) |
| 12 | Panel material count ≠ 2 |
| 13 | Panel binding not by vertex count (--invert-bind lands here) |
| 14 | Centroid off origin |
| 15 | Circumradius not uniform |
The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron, the needs-5.1 PR label, or manual dispatch). Smoke does not pass --output. Its catalog falsifier is --invert-bind (expects exit 13).