Lightmap UV Channel

examples/lightmap-uv-channel/

A market cart carrying the two-channel UV contract for baked lighting: UV0 untouched, UVLight packed with no overlaps and a respected margin.

Rendered headless by the example itself. Select it to enlarge.

witnesses Per-part UVMap/UVLight with active vs active_render pinned (ops clear both flags), UV0 drift 0.0, 3680 islands with 0 SAT overlaps, min island distance 0.00401.

tags uv game-pipeline rendering

runs on 4.5 LTS · 5.1 · 5.2 LTS

blender --background --python examples/lightmap-uv-channel/lightmap_uv_channel.py --

falsify it Breaks the contract on purpose and must exit 7

blender --background --python examples/lightmap-uv-channel/lightmap_uv_channel.py -- --overlap-islands

A runnable example building the second UV layer engines require for baked lighting, on an asset built to be reused: a market cart assembled from 24 named, watertight parts — a plank-grooved bed with side rails, corner brackets, and fasteners, spoked wheel assemblies (felloe ring + eight spokes + hub + iron tyre + bolt ring) on a capped axle, draw shafts with a crossbar and a prop leg, four posts, and a ribbed canvas canopy. Each post rises from inside the bed into the canopy's underside, its top sheared to the arc at its own station for a uniform 12 mm bite. Fixed 1.35 m posts used to stop 0.33 m short of the arc, so the canopy floated. The UV1 contract is unchanged: every part keeps its island count, zero overlap and minimum island distance. Ground-level pivot at z=0, identity transforms by construction, Cart.* datablocks. (An earlier revision shipped a soft uniform-tan cart with lumpy polygon wheels and was remodeled under the asset-quality gate; its wheel material was also silently never applied — a name-key lookup bug the exact per-part mapping now replaces.) UV0 is the texture channel (deterministic dominant-axis box projection per face); UVLight is the baked-lighting atlas (smart_project → lightmap_pack).

What it witnesses (all independently derived, nothing trusted from the packer):

What each check catches on failure: wrong layer names/count (exit 3); flags not re-established after the ops (exit 4); channel 0 touched by the UV1 unwrap (exit 5 — probed via the clobber trap, drift 2.068); UV1 loops outside the unit square (exit 6); overlapping islands (exit 7 — probed by translating an island onto a neighbor, 15 SAT hits); margin floor broken (exit 8 — probed with MARGIN_DIV=0, measured 2e-5 < 0.001); non-watertight part (exit 9).

Authoring hazards pinned while building this (probes in .scratch-style bisect scripts; the reasons the check code re-fetches layers by name and re-asserts flags):

Pipeline arc neighbors: UV-layer authoring in uv-layer-grid, tangent-space from UVs in triangulate-tangents, export round-trip in gltf-export-roundtrip, pivot discipline in prop-origin-transform.

Version witness: check output is byte-identical on Blender 4.5.11 LTS and 5.2.1 (24 parts, 3428 islands, drift 0, overlap 0, min island distance 0.00401). The UV layout is packer-version-dependent by design; the contracts are layout-independent invariants.

Render as proof: the cart beside its enlarged UV1 atlas display — a slate face in a walnut frame on a plinth with a brass UV1 LIGHTMAP plate, a faint quarter grid marking the [0,1] square the bounds check holds — the Bed's packed lightmap built from live UV data, so a change in the atlas moves the board geometry. The falsification variant (--falsify) translates the second-largest island onto the largest: a big emissive-red island visibly stacked over the atlas (15 SAT hits in the check probe). The render path also gates the asset itself through examples/gallery_asset_quality.py (naming, material variation, edge treatment — exit 11): 24 named parts, 5 materials, right-angle share 0.138. Wood, canvas and iron carry procedural grain, weave and pitting over Object coordinates; no check reads a material.

Run#

blender --background --python lightmap_uv_channel.py --
blender --background --python lightmap_uv_channel.py -- --overlap-islands   # falsifier: overlap scan reports the dragged island; exits 7
blender --background --python lightmap_uv_channel.py -- --output atlas.png
blender --background --python lightmap_uv_channel.py -- --falsify overlap.png

Exit codes#

Per-script sequential checks. 9 is a valid check code; there is no rule against it. 10 is the shared framing helper. 11 is the shared asset-quality helper.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3UV layer names ≠ UV0 / UV1
4UVMap / UV1 render/clone flags wrong after re-assert
5Channel 0 (UV0) touched by the UV1 unwrap
6UV1 loops outside [0,1]
7SAT overlap self-test failed, or UV1 triangle pairs overlap (--overlap-islands lands here)
8Min island distance below margin
9Part is not watertight
10Gallery framing violation (--output only; gallery_framing)
11Gallery asset-quality violation
12--output produced no file

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 or --falsify. Its catalog falsifier is --overlap-islands (expect exit 7): the same island drag as --falsify, applied before the SAT scan.

Source

examples/lightmap-uv-channel/lightmap_uv_channel.py View the script on GitHub →
A covered wooden cart on spoked wheels with draw shafts, beside a walnut-framed slate display showing its packed lightmap UV atlas as coloured islands on a quarter grid.