Hand Pump

showcase/hand-pump/

A procedural village hand pump whose gooseneck is a 6-gon tube from the lower-column radius, 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: 2412 tris, two materials with 1134 metal and 332 wood faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.610×0.521×1.055 m, grounded zmin, spout-to-column gap 2.4 mm, flange-to-plinth gap 6.0 mm, column radius 68 mm, LOD ratios in band, convex collider 146 tris, hygiene all zero, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z 16; --float-spout 17; --float-flange 18; --skinny-col 19.

category Village

tags mesh export showcase

blender --background --python showcase/hand-pump/hand_pump.py --

A showcase piece, not an example. Procedural cast-iron village hand pump (stepped wooden plinth, a turned lower barrel with a moulded foot and a cast spout boss, a fluted upper barrel socketed through a moulded collar, 6-gon gooseneck tube from the lower-column radius, a stuffing-box head under a domed cap and ball finial, a handle whose tail runs past the fulcrum to a counterweight ball, a turned wooden grip, and a coopered bucket with two iron hoops on the ground under the spout) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

The column stays on the origin; only zmin is snapped. The gooseneck is a tube about named stations on the lower-column radius, not a chain of cylinders. The flange and the plinth cap bite their hosts so stacked caps are not coplanar.

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). Hygiene combinatorics match examples/mesh-hygiene-audit (copied, not imported).

Intended size: ~1.05 m village pump, 0.34 m plinth, 0.76 m column, 0.15 m bucket; outer AABB 0.610 × 0.521 × 1.055 m.

Budgets#

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

AxisDeclaredMeasured (4.5.11 / 5.2.1; 5.1.2 not re-run after the model pass)
Base triangles900–26002412 / 2412
LOD1 ratio0.32–0.62 of base0.5000 / 0.5000
LOD2 ratio0.10–0.35 of base0.2197 / 0.2181
Materialsexactly 2 distinct, ≥48 metal, ≥24 wood2 slots, 1134 metal / 332 wood
UVsin 0..1, AABB overlap ≤ 1e-5in range, overlap 0
Outer AABB(0.610, 0.521, 1.055) m ± 0.015(0.6100, 0.5210, 1.0545), zmin 0
Collider tris≤ 220 (source turns the bucket at 8 staves, no hoops)146
Exportwritten, size > 0200352 / 200352 / 200348 bytes (4.5.11 / 5.1.2 / 5.2.1)

The model pass took the pump from 1064 to 2412 triangles and raised the ceiling 2200 → 2600. Two lighting-and-paint passes had left it the plainest object in its asset sheet: a smooth two-step column on a box. It now carries the cast detail a village pump has: a turned lower barrel with a moulded foot and a spout boss, eight flutes on the upper barrel, a moulded collar at the socket, a domed cap and ball finial on the head, a rolled drip lip on the nozzle, a counterweighted tail on the handle, and a turned grip. A coopered bucket with two iron hoops stands under the spout, clear of the plinth. The spout's reach grew 0.13 → 0.17 m (SPOUT_R) so the nozzle clears the plinth and falls inside the bucket mouth. The outer AABB grew in y (0.382 → 0.521 m) for the bucket and in z (1.052 → 1.055 m) for the finial. The collider stays under its 220 ceiling because its source turns the bucket at 8 staves without hoops (BUCKET_PROXY_SEGS); at 16 staves the hull ran to 267, past Unity's 255 convex-collider limit. The foot (0.068 m) and spout boss (0.067 m) stay within COL_R_TOL of COL_R_LO, so the column-radius budget still reads the barrel. The flange classifier now also requires a shell centred on the column axis: the bucket's lower hoop is as wide and as low as the flange, and would otherwise mask --float-flange. The new bucket budget gets its own falsifier, --shift-bucket (exit 20).

Earlier, base triangles dropped from 1164 to 1064 in the quality pass: seven sausage cylinders became one gooseneck tube, and the wood grip is no longer beveled with the plinth. Outer AABB is 0.610 × 0.382 × 1.052 m (was 0.610 × 0.364 × 1.049 m).

DECIMATE COLLAPSE triangle counts are not identical across series — 5.2.1 is slightly leaner on LOD2. 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; the only RNG is the seeded per-piece wood tone. Bevel inputs are sorted by edge index, so the face order is the same on every run; a Python set of edges handed to the bevel had made it vary. Export byte counts differ on 5.2.1 (glTF serializer), not a gated axis.

Surface and stage#

The quality pass found no geometric defect: every joint checked in the inspection sheet (spout root, flange bolts, head and handle pivot, column step, plinth) seats as the joint budgets say. The defects were on the surface. The iron was polished metal (metallic 1.0, roughness 0.38) and read as chrome; the next pass painted it near-black, which vanished into the dark stage. It is now dark green enamelled cast iron with rust in the pores and at the edges, and a cool rim light aimed at the column lifts its silhouette off the backdrop. The plinth slabs and the handle grip carry their own wood tone and grain. The stage grid grew from 14 to 60 m, because the wall's left edge showed in the corner of the hero. The temp .glb is removed after its size is measured. With no new geometric defect, there is no new budget.

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
Plinth zminwithin 1e-4 of 00.00000

Joint fit and column#

AxisDeclaredMeasured (all three)
Spout-to-column BVH gap≤ 0.008 m0.00239
Flange-to-plinth BVH gap≤ 0.008 m0.00600
Lower-column radius vs COL_R_LO± 0.008 m (widest barrel shell, the moulded foot)0.06800
Bucket under the spoutone bucket; nozzle (axis + lip radius) inside the mouth by ≥ 0.015 m; bucket zmin within 1e-4 of 0; ≥ 0.004 m clear of the plinthcatch 0.01900; zmin 0; clear 0.02200

Falsifiers#

Each violates one named budget. All were run on 4.5.11 and 5.2.1 after the model pass and returned the same code on each (5.1.2 not re-run).

FlagBudget violatedExit
--skip-decimateLOD1 ratio band9
--stray-vertloose vertex count is 015
--lift-zbounding box zmin is 016
--float-spoutspout-to-column BVH gap17
--float-flangeflange-to-plinth BVH gap18
--skinny-collower-column radius19
--shift-bucketbucket under the spout20

Run#

blender --background --python hand_pump.py --
blender --background --python hand_pump.py -- --skip-decimate
blender --background --python hand_pump.py -- --stray-vert
blender --background --python hand_pump.py -- --lift-z
blender --background --python hand_pump.py -- --float-spout
blender --background --python hand_pump.py -- --float-flange
blender --background --python hand_pump.py -- --skinny-col
blender --background --python hand_pump.py -- --shift-bucket
blender --background --python hand_pump.py -- --output hand-pump.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–20 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 ≠ 2 distinct slots, or wood/metal faces missing
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
15Hygiene (--stray-vert lands here)
16Grounded zmin / plinth (--lift-z)
17Spout-to-column gap (--float-spout)
18Flange-to-plinth gap (--float-flange)
19Column radius (--skinny-col)
20Bucket under the spout (--shift-bucket)
21Gallery asset-quality violation (render path; the floors' 11 is remapped here)

Source

showcase/hand-pump/hand_pump.py View the script on GitHub →
A green-enamelled cast-iron village pump with a fluted barrel, ball finial and counterweighted handle on a wooden plinth, a hooped bucket under its spout.