Tavern Stool

showcase/tavern-stool/

A procedural tavern stool with a lathed seat, turned splayed legs, staggered stretchers, and iron ferrules gripping each leg on its own axis over a level tread, 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: 3040 tris, two materials with 1168 wood and 576 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.341×0.341×0.470 m, LOD ratios in band, convex collider 308 tris, non-empty glTF. Hygiene all zero, four treads at zmin 0, stretcher-leg gap 0, ferrule grip 1.20 mm on each leg's axis, leg ends 1.1 mm above their treads, bake 22.4 texels per UV cell. --float-stretchers exits 17; --pipe-ferrule 18; --sink-legs 20; --low-bake 21.

category Village

tags mesh export showcase

blender --background --python showcase/tavern-stool/tavern_stool.py --

A showcase piece, not an example. A procedural tavern stool: a lathed round seat, turned splayed legs, tenoned stretchers at two heights, and iron ferrules on level treads. Then the shipped pipeline: unique-cell UVs, a Cycles high-to-low normal bake, an LOD chain, a convex collider and a Unity glTF export.

Legs are one loft per leg, not stacked cones. Each foot is an iron ferrule sleeve on the leg's own axis, its inner wall a named grip (1.2 mm) inside the leg. The sleeve's raked bottom rim is buried in a level iron tread, and the leg ends inside the sleeve, above the tread. The first build ended every leg 3 mm above a vertical ring on the floor, with a 1 mm air gap around the foot. The front-back and side stretchers sit at different heights, so their tenons do not meet inside the leg. Each member has its own wood tone and grain. The seat is dished 14 mm at the centre for sitting, from the same lathe ring count as the old 6 mm dish, so the triangle count did not move. The dish floor stays 8 mm above the leg tenons wherever a leg enters from below.

The hero stands the stool level and turns it about Z only. An earlier still rolled it 6° about X to show the seat, which sank one pair of treads into the floor and lifted the other; the camera now sits higher instead. A pewter tankard stands in the dish as render-only dressing. It is not part of the asset: no budget, no export, and the asset sheet renders the stool without it.

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 its value from the mesh, materials, UVs, evaluated LOD, collider or export file.

AxisDeclaredMeasured (4.5.11 / 5.1.2 / 5.2.1)
Base triangles2750–33503040 / 3040 / 3040
LOD1 ratio0.32–0.62 of base0.5000 / 0.5000 / 0.5000
LOD2 ratio0.10–0.35 of base0.2197 / 0.2197 / 0.2197
Materialsexactly 2 distinct; ≥200 wood, ≥48 metal faces2 slots; 1168 / 576
UVsin 0..1, AABB overlap ≤ 1e-5in range, overlap 0
Outer AABB(0.341, 0.341, 0.470) m ± 0.015(0.3406, 0.3406, 0.4700), zmin 0
Collider tris≤ 640308
Exportwritten, size > 0251188 / 251188 / 251184 bytes (4.5.11 / 5.1.2 / 5.2.1)

Base triangles rose from 2528 to 3040 in the second quality pass, and the band moved from 2300–3200 to 2750–3350. Each foot gained a tread, and the sleeves are walled on both ends. The bake went from 256 px to 1024 px, and its cage extrusion from 60 mm to 10 mm. See the bake texel budget below.

DECIMATE COLLAPSE triangle counts are not identical across series. The gate is a ratio band, not an exact count. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction is closed-form and member tones are seeded, so repeated runs on one binary print identical measurements. Export byte counts may differ by a few bytes across series.

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
Named supports: 4 treads (plus 4 sleeves)each zmin ≤ 1e-38, cup_z 0.00000
Seat0.340 m dia × 0.470 m high ± 0.020.3400 × 0.4700

Joint fit, feet and bake#

AxisDeclaredMeasured (all three)
Stretcher-leg BVH gap≤ 0.008 m0.00000
Ferrule grip (exit 18): leg radius where the sleeve wraps it, less the sleeve's inner radius, radially about the leg's own axis; 4 sleeves0.0006–0.0025 m0.00120
Foot stack (exit 20): leg end above its tread's top / sleeve bottom buried below it; 4 legs, 4 treads≥ 0.0005 m / ≥ 0.001 m0.00113 / 0.00943
Bake texel density (exit 21): smallest UV cell in baked texels≥ 12 px22.43 px (1024 px bake)

At 256 px the bake gave each of the 1,744 UV cells about 6 px. The hero then sampled the neighbouring cells' normals across cell borders, which showed as dark slivers on the legs above the ferrules.

Falsifiers#

Each violates one named budget. All 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
--short-legsnamed ferrule supports at Z=016
--float-stretchersstretcher-leg join17
--pipe-ferruleferrule grip (a pipe ring on the tread; measured −0.00011)18
--sink-legsfoot stack (leg ends 10 mm into the tread; measured −0.00887)20
--low-bakebake texel density (256 px bake; measured 5.61 px)21

--pipe-ferrule keeps the treads, so the triangle band and the envelope cannot steal its failure. --sink-legs keeps the leg inside its sleeve, so the grip still passes.

Run#

blender --background --python tavern_stool.py --
blender --background --python tavern_stool.py -- --skip-decimate
blender --background --python tavern_stool.py -- --stray-vert
blender --background --python tavern_stool.py -- --lift-z
blender --background --python tavern_stool.py -- --short-legs
blender --background --python tavern_stool.py -- --float-stretchers
blender --background --python tavern_stool.py -- --pipe-ferrule
blender --background --python tavern_stool.py -- --sink-legs
blender --background --python tavern_stool.py -- --low-bake
blender --background --python tavern_stool.py -- --output stool.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. 20 and 21 are this piece's own.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Mesh did not build / no UV layer
4Base triangle count outside range
5Material count ≠ 2 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, or a named ferrule support floats
17Joint fit: stretcher-leg gap
18Ferrule grip out of band, or a sleeve missing (--pipe-ferrule)
19Seat diameter or height off the stated real-world size
20Foot stack: leg end below its tread's top, or sleeve not buried (--sink-legs)
21Bake texel density below floor (--low-bake)
22Gallery asset-quality violation (render path; the floors' 11 is remapped here)

Source

showcase/tavern-stool/tavern_stool.py View the script on GitHub →
A wooden tavern stool with a grained, dished round seat holding a pewter tankard, turned splayed legs, stretcher rungs at two heights, and dark iron ferrules on flat treads.