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/uv-layer-grid/
The UV-layer authoring hazard — bmesh.ops.create_grid(..., calc_uvs=True) is a silent no-op unless a UV layer already exists; without one an Image Texture samples texel (0,0) everywhere.
Rendered headless by the example itself. Select it to enlarge.
runs on 4.5 LTS · 5.1 · 5.2 LTS
blender --background --python examples/uv-layer-grid/uv_layer_grid.py --
falsify it Breaks the contract on purpose and must exit 3
blender --background --python examples/uv-layer-grid/uv_layer_grid.py -- --precreate-on-hazard
A runnable example that witnesses the UV-layer authoring hazard behind bmesh.ops.create_grid(..., calc_uvs=True): the flag is a silent no-op unless a UV layer already exists on the BMesh. AI-generated Blender code commonly trusts calc_uvs=True, wires an Image Texture, and ships a mesh that renders as one flat color — every fragment samples texel (0, 0).
Found while authoring image-pixels-testcard; lifted into its own smoke-gated witness so the contract cannot quietly drift.
What it witnesses: three related contracts on the same grid topology (SEG×SEG faces, (SEG+1)² verts, size 1.0 → coords in [-1, 1]):
create_grid(..., calc_uvs=True) with no prior bm.loops.layers.uv.new(...) leaves len(bm.loops.layers.uv) == 0 and mesh.uv_layers empty after to_mesh.calc_uvs=True fills every loop. UVs must match the closed form u = (x/size + 1)/2, v = (y/size + 1)/2 within 1e-6, both on the BMesh and after persisting to mesh.uv_layers.active.data.calc_uvs=False, then uv.new("UVMap") and a loop write of the same closed form. Does not depend on calc_uvs at all; same tolerance.What each check catches on failure:
calc_uvs=True starts creating a layer on its own (falsified: expecting n_layers == 0 fails if a layer appears; temporarily pre-creating a layer before the hazard probe exits 3).create_grid segment/size contract drift (wrong face/vert counts).calc_uvs filling a different parameterization, or a one-texel shift (falsified: adding 0.1 to every U exited 6 with measured error 0.1).to_mesh.--output) — the saved PNG is read back and probed at each panel's projected center: the hazard panel must be one flat teal (per-channel spread ≤ 0.02, ordering b>g>r), the repaired panel a high-contrast checker (spread ≥ 0.25). Falsified twice: secretly giving the hazard panel UVs exited 11 with measured spread 0.7333; repainting texel (0,0) magenta exited 12 with measured rgb (0.927, 0.118, 0.536).Version divergence: none probed — the silent no-op and closed-form UV fill assert identically on Blender 4.5 LTS and 5.1. The only gate in the file is the EEVEE engine id for the optional render (BLENDER_EEVEE_NEXT on 4.x, BLENDER_EEVEE on 5.x).
Render: two oak studio easels, each holding a walnut-framed tile panel that shares one neon checker image, with a brass placard on the easel ledge naming the authoring path. Left, NO UV LAYER, is the hazard — calc_uvs=True alone, so the whole panel is the flat teal of texel (0, 0). Right, UV LAYER FIRST, is the repair (pre-create + calc_uvs) — the full magenta/cyan checker. If the UV contract failed, both panels would read the same — and the still is not just an illustration: the script re-reads its own render and exits non-zero unless the pixels prove the flat-vs-checker split (measured on 5.2.1: hazard spread 0.0078, repair spread 0.7569; identical on 4.5.11). The render path gates framing through examples/gallery_framing.py (exit 10) before writing the still.
# Cheap correctness check (no render) — the CI check:
blender --background --python uv_layer_grid.py --
# Falsifier: pre-create a UV layer on the silent-no-op probe. Must exit non-zero.
blender --background --python uv_layer_grid.py -- --precreate-on-hazard
# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
blender --background --python uv_layer_grid.py -- --output uv.png
blender --background --python uv_layer_grid.py -- --output uv.png --engine cycles
Per-script sequential checks. 9 is a valid check code; there is no rule against it. 10 is the shared gallery framing gate; 15 is the missing-render-file check.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Uncaught exception (FATAL wrapper) |
| 2 | argparse / usage |
| 3 | Silent-no-op hazard gone (--precreate-on-hazard lands here) |
| 4 | Grid topology drifted |
| 5 | Pre-create + calc_uvs did not persist one UV layer |
| 6 | calc_uvs closed-form error |
| 7 | Mesh UV round-trip error |
| 8 | calc_uvs=False unexpectedly created a UV layer |
| 9 | Explicit UV assignment error |
| 10 | Framing gate violation on the --output path (gallery_framing) |
| 12 | Broken panel is not texel-(0,0) teal |
| 13 | Repaired panel is not a checker |
| 14 | Broken and repaired panels render identically |
| 15 | --output produced no file |
| 16 | Broken panel is not flat |
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 --precreate-on-hazard (expects exit 3).