Chopping Block

showcase/chopping-block/

A procedural log-round chopping block with a riveted iron hoop and a felling axe buried in the sawn face, through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

Rendered headless by the showcase piece itself. Select it to enlarge.

witnesses Recomputed: 1816 tris, four materials with 180 bark / 216 grain / 412 metal / 176 haft faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.526 x 0.526 x 1.006 m, LOD ratios in band, convex collider 398 tris, non-empty glTF. Hygiene: zero non-manifold edges, loose geometry, doubles, zero-area faces, n-gons and coplanar disjoint face pairs, grounded at zmin 0, log plumb to 1e-7, oval haft 1.35 wide-to-thick. Joint fit: six shells, 13.5 mm haft clearance in the eye, 78.7 mm bit bury, hoop biting 3.7-4.2 mm into the log on every segment, two lap rivets seated 1.50 and 1.54 mm. --fat-haft exits 17 on eye clearance; --round-band and --float-rivets exit 18 on their seats; --round-haft exits 19 on the haft section.

category Village

tags mesh export showcase

blender --background --python showcase/chopping-block/chopping_block.py --

A showcase piece, not an example. Procedural chopping block (a log round with a riveted iron hoop and a felling axe buried in the sawn face) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

The log is an out-of-round loft whose radius is a closed-form function of angle and height; the hoop, its rivets, the top rim and the drying checks are all generated from that same function, so they stay seated when a dimension changes. The hoop is a lapped strip with two domed rivets through the lap. The axe head is one lofted shell from poll to bit with its chamfers modelled into the section profile, and the haft is swept through the eye rather than pushed into it. The haft is oval, bows away from the bit, and hooks toward it at the knob.

The hero sets the block to work: three split billets lie at its foot, each a quarter of a small round laid on one split face or the other so some show bark and some the split, among twelve seeded chips. Each billet is its own object, smooth on the bark and flat on the split, with sawn ends ringed about its own pith; the chips are wedges, thick at the struck end and feathered at the tip, tilted as they fell, some with bark on top. Flat boxes read as confetti, and one striped mesh made the billets look like planks. The dressing is render-only with its own materials (the block's materials sample its baked normal map through UVs the dressing does not have). It is not part of the asset: no budget reads it, the export does not carry it, and the asset sheet renders the block alone.

The end grain only covers faces that look up. The top chamfer and rim chips lean out toward the bark and take its cambium brown; pale end grain wrapping down them drew a lid on a canister. The top is weathered grey-brown rather than fresh pine, and the iron's albedo sits near 0.3: at 0.1 the metallic head mirrored the black stage and read as a hole in the frame.

It asserts budget conformance of the generated result. It does not witness an API contract. "It rendered without error" is not a check.

Composes skills mesh-editing-and-bmesh, bake-high-to-low, depsgraph-and-evaluated-data, engine-export-presets, and snippets bake_normal_high_to_low.py, setup_bake_target_image.py, lod_chain.py / decimate_to_budget.py, convex_hull_collider.py, export_preset_unity.py (helpers copied, not imported as a package).

Budgets#

Declared as named constants; every gate recomputes from the mesh, materials, UVs, evaluated LOD, collider, or export file.

AxisDeclaredMeasured (4.5.11 / 5.1.2 / 5.2.1)
Base triangles1730–18701816 / 1816 / 1816
LOD1 ratio0.32–0.62 of base0.5000 / 0.5000 / 0.5000
LOD2 ratio0.10–0.35 of base0.2192 / 0.2192 / 0.2192
Materialsexactly 4 distinct; ≥160 bark, ≥90 grain, ≥170 metal, ≥150 haft faces4 slots; 180 / 216 / 412 / 176
UVsin 0..1, AABB overlap ≤ 1e-5in range, overlap 0
Outer AABB(0.526, 0.526, 1.006) m ± 0.012(0.5264, 0.5264, 1.0064)
Collider tris≤ 560398
Exportwritten, size > 0174020 / 174020 / 174020 bytes (4.5.11 / 5.1.2 / 5.2.1)

The triangle band, the outer AABB and the collider ceiling were re-fitted in the quality pass that added the lap, the rivets and the 16-segment oval haft (1472 → 1800 triangles, collider 434 → 538). The triangle band is centred on that measurement, ± 4 %. The axe-head reprofile (a waist behind the eye) added one head section: 1800 → 1816.

Hygiene#

Recomputed from the generated mesh, not asserted about the script.

AxisDeclaredMeasured (all three)
Non-manifold edges00
Loose verts / edges0 / 00 / 0
Doubles merged at 1e-500
Zero-area faces00
N-gons00
Coplanar disjoint face pairs00
Grounded: zminwithin 1e-4 of 00.0000
Log axis out of plumb≤ 1e-50.0000000
Log size0.535 × 0.360 m ± 0.030 / ± 0.0100.5464 × 0.3600
Haft section, wide over thick at the grip1.25–1.601.3500

Joint fit#

Six shells that have to meet correctly: log, hoop, head, haft, and the two rivets through the hoop's lap.

AxisDeclaredMeasured (all three)
Shell countexactly 66
Haft clearance inside the eye≥ 0.006 m0.01350
Haft engagement through the eye≥ 0.020 m0.06925
Haft breakout margin below the head≥ 0.006 m0.00875
Bit bury below the sawn top≥ 0.030 m0.07871
Bit inset from the rim≥ 0.030 m0.16808
Hoop bite into the log, every segment0.002–0.007 m0.00371–0.00422
Haft clearance above the log≥ 0.015 m, 0 verts inside0.07531, 0
Rivet seat into the hoop, each rivet0.0008–0.0025 m0.00150, 0.00154
Rivet dome proud of the hoop, each rivet≥ 0.0025 m0.00419, 0.00432

The hoop bite is binned by angular segment against the log's own radius function. A single global midpoint radius misclassifies outer chamfer vertices as inner ones on an out-of-round log, which is how a hoop that visibly floated on one side still passed.

The rivet seat is measured radially at each rivet vertex's own angle, against the hoop's outer face read off the mesh by a ray cast from the axis. Each rivet is aimed down the hoop's surface normal, not the radial: the log is out of round, so across one 12 mm head its surface falls by up to 1.4 mm, and radially aimed heads seated 1.87 and 2.82 mm against a 1.5 mm design bite, one edge sunk and the other lifted.

DECIMATE COLLAPSE triangle counts happen to agree across all three series here; the gate is still a ratio band, not an exact count. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG: two runs on each binary print identical measurements, and the three binaries agree on every gated axis. Export byte counts differ by 16 B on 5.2.1 (glTF serializer), not a gated axis.

Shading and materials#

Only the axe head is flat-shaded: it is a chamfered forging, and its facets are its form. The log and the hoop are smooth-shaded. Flat-shaded, the log's 36 equal facets read as coopered staves and the block as a tub, and the hoop threw one highlight per facet. The log's wobble still reads in the silhouette, and its surface is carried by the materials: vertical bark furrows with a bump, growth rings about an off-centre pith on the sawn face, and dark radial checks drawn at the same angles the geometry notches. Every material boundary is a hard edge, so the sawn rim stays crisp against the bark.

The haft has its own material slot. It used to share the bark's, which made the hickory handle the darkest wood on the piece.

Falsifiers#

Each violates one named budget. All seven were run on 4.5.11, 5.1.2 and 5.2.1 and returned the same code on each.

FlagBudget violatedExit
--skip-decimateLOD1 ratio band9
--stray-vertloose vertex count is 015
--lift-zbounding box zmin is 016
--fat-hafthaft clearance inside the eye17
--round-bandhoop bite into the log18
--float-rivetsrivet seat into the hoop18
--round-hafthaft section wide over thick19

--fat-haft thickens the haft across the cheeks only, which is the dimension the eye clearance measures. Scaling the whole oval section grew the knob 16 mm past the bounding box, so it exited 8 instead.

Run#

blender --background --python chopping_block.py --
blender --background --python chopping_block.py -- --skip-decimate
blender --background --python chopping_block.py -- --stray-vert
blender --background --python chopping_block.py -- --lift-z
blender --background --python chopping_block.py -- --fat-haft
blender --background --python chopping_block.py -- --round-band
blender --background --python chopping_block.py -- --float-rivets
blender --background --python chopping_block.py -- --round-haft
blender --background --python chopping_block.py -- --output chopping-block.png

Smoke passes no flags.

Exit codes#

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path. 15–19 are the hygiene and joint-fit family.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Mesh did not build / no UV layer
4Base triangle count outside range
5Material count ≠ 4 distinct slots, or a face-count floor missed
6UVs outside 0..1
7UV AABB overlap above tolerance
8World AABB off declared outer size
9LOD ratio band (--skip-decimate lands here)
10Framing gate (render path only)
11Collider triangle count above ceiling
12Bake did not finish or image has no data
13Export file missing or empty
14--output produced no file
15Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, z-fight
16Not grounded: bounding box zmin off 0
17Joint fit: shell count, eye clearance, engagement, breakout, bury, inset
18Contact fit: hoop bite band, haft fouling the log, or a rivet off its seat band
19Log out of plumb, off its stated real-world size, or a round haft section
20Gallery asset-quality violation (render path; the floors' 11 is remapped here)

Source

showcase/chopping-block/chopping_block.py View the script on GitHub →
A log-round chopping block bound with a riveted iron hoop, a felling axe buried in its ringed top, split billets and wood chips scattered at its foot.