UV Layer Grid

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.

witnesses calc_uvs=True alone leaves 0 UV layers; pre-create + calc_uvs fills loops to the closed-form grid UVs ((x/size+1)/2, (y/size+1)/2) within 1e-6; explicit loop assignment is the calc_uvs-free fallback. The still is self-witnessing: the script re-reads its render and exits non-zero unless the hazard panel is one flat teal and the repaired panel a high-contrast checker.

tags mesh bmesh uv materials

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]):

What each check catches on failure:

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.

Run#

# 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

Exit codes#

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.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Silent-no-op hazard gone (--precreate-on-hazard lands here)
4Grid topology drifted
5Pre-create + calc_uvs did not persist one UV layer
6calc_uvs closed-form error
7Mesh UV round-trip error
8calc_uvs=False unexpectedly created a UV layer
9Explicit UV assignment error
10Framing gate violation on the --output path (gallery_framing)
12Broken panel is not texel-(0,0) teal
13Repaired panel is not a checker
14Broken and repaired panels render identically
15--output produced no file
16Broken 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).

Source

examples/uv-layer-grid/uv_layer_grid.py View the script on GitHub →
Two walnut-framed tile panels on oak easels with brass placards: NO UV LAYER on the left, flat teal, and UV LAYER FIRST on the right, a full magenta, cyan and green checker.