Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/gallery/assets/chopping-block-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,245 changes: 953 additions & 292 deletions docs/gallery/chopping-block/index.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1096,12 +1096,12 @@ <h2><a href="signpost/">signpost</a></h2>
</article>
<article class="card" data-tags="mesh export showcase">
<a class="card-media" href="chopping-block/" aria-label="chopping-block example detail page">
<img src="assets/chopping-block-hero.webp" alt="chopping-block — A procedural chopping block with an embedded axe through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract." loading="lazy" decoding="async" />
<img src="assets/chopping-block-hero.webp" alt="chopping-block — A procedural log-round chopping block with an iron hoop and a felling axe buried in the sawn face, through UVs, bake, LOD, collider, and Unity glTF, asserting" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="chopping-block/">chopping-block</a></h2>
<p class="teaches">A procedural chopping block with an embedded axe through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 2016 tris, two materials with 122 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.471×0.463×0.841 m, LOD ratios in band (5.2 COLLAPSE 2 tris leaner on LOD2), convex collider 188 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.</p>
<p class="teaches">A procedural log-round chopping block with an 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.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 1472 tris, three materials with 252 bark / 252 grain / 288 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.526 x 0.526 x 1.012 m, LOD ratios in band, convex collider 434 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. Joint fit: four shells, 18.0 mm haft clearance in the eye, 78.7 mm bit bury, hoop biting 3.7-4.0 mm into the log on every segment. --fat-haft exits 17 on eye clearance; --round-band exits 18 on the hoop bite band.</p>
<a class="card-link" href="chopping-block/">View example <span aria-hidden="true">&rarr;</span></a>
</div>
</article>
Expand Down
46 changes: 39 additions & 7 deletions showcase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,22 @@ entry in `showcase/gallery.json`, and a rendered still.
falsifier (exit 9), `--stray-vert` the mesh-hygiene falsifier (exit
15), `--lift-z` the grounded-zmin falsifier (exit 16), and
`--fat-rungs` (or the piece's equivalent) the joint-fit falsifier
(exit 17). A budget with no falsifier witnesses nothing: prove each
one fails once, and check the exit code, not just non-zero.
(exit 17), and `--round-band` (or equivalent) the seat-conformance
falsifier (exit 18). A budget with no falsifier witnesses nothing:
prove each one fails once, and check the exit code, not just
non-zero.
- **Hygiene budgets.** Copied combinatorics from
`examples/mesh-hygiene-audit` (do not import the example). Every piece
asserts on the generated mesh: non-manifold edges 0, loose verts 0,
loose edges 0, doubles at 1e-5 0, zero-area faces 0, n-gons 0, world
AABB min Z within 1e-4 of 0. Multi-body props do **not** require Euler
characteristic 2 — that is a single-shell contract. Pairs of parts
meant to touch assert a BVH surface gap below a named epsilon
(vert-vert is the wrong metric for thin straps and collars).
AABB min Z within 1e-4 of 0, and coplanar disjoint face pairs 0. Count
the last one over faces that share **no** vertex: the triangles of one
flat fan cap are coplanar and close-centred by construction, and
counting those makes the budget unsatisfiable rather than meaningful.
Multi-body props do **not** require Euler characteristic 2 — that is a
single-shell contract. Pairs of parts meant to touch assert a BVH
surface gap below a named epsilon (vert-vert is the wrong metric for
thin straps and collars).
- **Joint-fit budgets.** Parts that interpenetrate on purpose — a tenon
in a mortise, a peg in a hub — assert how deep the overlap goes, not
just that it exists. Split the mesh into shells by edge connectivity,
Expand All @@ -58,6 +64,25 @@ entry in `showcase/gallery.json`, and a rendered still.
the host's chamfer as a spike — that is the class this catches.
Measure in the construction frame (un-rotate by any rake) so a tilted
host does not inflate its own AABB.
- **Seat conformance (exit 18).** A band, hoop, strap or collar wrapped
around a host asserts a **banded** seat depth — a minimum so it cannot
float and a maximum so it cannot sink — sampled per angular segment
rather than as a single global figure. Derive the wrapper's profile
from the host's own radius function instead of a circle, and classify
inner versus outer vertices against the host surface at each vertex's
own angle. A global midpoint radius misclassifies outer chamfer
vertices as inner ones the moment the host is out of round, which is
how a hoop that visibly gapped on one side still passed. The paired
falsifier makes the wrapper a true circle on an out-of-round host.
- **Plumb and real-world size (exit 19).** Assert that the axis of a
turned or lofted body is vertical, by comparing the XY centroid of a
bottom slab against a top slab — not the exact `zmin` and `zmax`
rings, which may be a handful of vertices once a rim is notched or
chipped. Separately assert the body's own diameter and height against
the dimensions the README states in metres, with a named tolerance.
The outer AABB does not cover this: on a prop with an appendage the
AABB is the appendage, and the body can drift to any size underneath
it.
- **Material face floors.** Every declared material asserts a named
minimum face count on the finished mesh, recomputed from
`polygon.material_index`. This catches the slot-assignment wipe class:
Expand All @@ -67,7 +92,14 @@ entry in `showcase/gallery.json`, and a rendered still.
append/replace, never clear-and-rebuild.
- **Exit codes** are file-local: `0` success, argparse `2`, `3` and above
in check order. `9` is legal. FATAL `sys.exit(1)` is a crash, never a
named check.
named check. `15`–`19` are reserved across pieces for the hygiene
family above: `15` hygiene, `16` grounded, `17` joint fit, `18` seat
conformance and contact, `19` plumb and real-world size.
- **Shading is part of the model.** A flat-shaded low-poly body and a
smooth-shaded one are different objects to a viewer. Smoothing a
wobbled 36-gon erases every bit of surface modelling in it and leaves
a featureless drum, so decide per part and say why. Nothing here is
measurable, which is exactly why it has to be looked at.
- **Rendered still and gallery entry.** Showcase pieces are visual by
definition. The pathology / sidecar exemption does not apply. Call
`examples/gallery_framing.check_framing` on the `--output` path only.
Expand Down
103 changes: 83 additions & 20 deletions showcase/chopping-block/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Chopping block

A showcase piece, not an example. Procedural chopping block (hooped
timber stump with an embedded axe) then the shipped pipeline:
unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex
collider, Unity glTF export.
A showcase piece, not an example. Procedural chopping block (a hooped
log round with 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, the top rim and the drying checks are all
generated from that same function, so they stay seated when a dimension
changes. 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.

It asserts **budget conformance** of the generated result. It does not
witness an API contract. "It rendered without error" is not a check.
Expand All @@ -21,38 +28,89 @@ materials, UVs, evaluated LOD, collider, or export file.

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) |
| --- | --- | --- |
| Base triangles | 1880–2160 | 2016 / 2016 / 2016 |
| Base triangles | 1410–1530 | 1472 / 1472 / 1472 |
| LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 |
| LOD2 ratio | 0.10–0.35 of base | 0.2192 / 0.2192 / 0.2183 |
| Materials | exactly 2 distinct, ≥24 metal faces | 2 slots, 122 metal |
| LOD2 ratio | 0.10–0.35 of base | 0.2188 / 0.2188 / 0.2188 |
| Materials | exactly 3 distinct; ≥160 bark, ≥90 grain, ≥24 metal faces | 3 slots; 252 / 252 / 288 |
| UVs | in `0..1`, AABB overlap ≤ 1e-5 | in range, overlap 0 |
| Outer AABB | (0.471, 0.463, 0.841) m ± 0.01 | (0.4713, 0.4626, 0.8413), zmin 0 |
| Collider tris | ≤ 220 | 188 |
| Export | written, size > 0 | 157000 / 157000 / 156992 bytes |
| Outer AABB | (0.526, 0.526, 1.012) m ± 0.01 | (0.5264, 0.5264, 1.0117) |
| Collider tris | ≤ 470 | 434 |
| Export | written, size > 0 | 150784 / 150784 / 150776 bytes |

### Hygiene

DECIMATE COLLAPSE triangle counts are **not** identical across series —
5.2.1 is 2 tris 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 uses no RNG. Export byte
counts differ by 8 B on 5.2.1 (glTF serializer), not a gated axis.
Recomputed from the generated mesh, not asserted about the script.

| Axis | Declared | Measured (all three) |
| --- | --- | --- |
| Non-manifold edges | 0 | 0 |
| Loose verts / edges | 0 / 0 | 0 / 0 |
| Doubles merged at 1e-5 | 0 | 0 |
| Zero-area faces | 0 | 0 |
| N-gons | 0 | 0 |
| Coplanar disjoint face pairs | 0 | 0 |
| Grounded: `zmin` | within 1e-5 of 0 | 0.0000 |
| Log axis out of plumb | ≤ 2e-4 | 0.0000000 |
| Log size | 0.535 × 0.360 m ± 0.03 / ± 0.02 | 0.5464 × 0.3600 |

`--skip-decimate` skips the LOD DECIMATE stage so LOD1 ratio is 1.0 and
exit 9 fires. That is the named budget the falsifier violates.
### Joint fit

Four shells that have to meet correctly: log, hoop, head, haft.

| Axis | Declared | Measured (all three) |
| --- | --- | --- |
| Shell count | exactly 4 | 4 |
| Haft clearance inside the eye | ≥ 0.006 m | 0.01800 |
| Haft engagement through the eye | ≥ 0.020 m | 0.08049 |
| Haft breakout margin below the head | ≥ 0.006 m | 0.02351 |
| Bit bury below the sawn top | ≥ 0.030 m | 0.07871 |
| Bit inset from the rim | ≥ 0.030 m | 0.14647 |
| Hoop bite into the log, every segment | 0.002–0.007 m | 0.00371–0.00400 |
| Haft clearance above the log | ≥ 0.015 m, 0 verts inside | 0.07858, 0 |

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.

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. Export byte counts differ
by 8 B on 5.2.1 (glTF serializer), not a gated axis.

### Falsifiers

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

| Flag | Budget violated | Exit |
| --- | --- | --- |
| `--skip-decimate` | LOD1 ratio band | 9 |
| `--stray-vert` | loose vertex count is 0 | 15 |
| `--lift-z` | bounding box `zmin` is 0 | 16 |
| `--fat-haft` | haft clearance inside the eye | 17 |
| `--round-band` | hoop bite into the log | 18 |

## Run

```bash
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 -- --output chopping-block.png
```

Smoke does not pass `--output` or `--skip-decimate`.
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.
`gallery_framing.check_framing` on the `--output` path. `15`–`19` are the
hygiene and joint-fit family.

| Code | Meaning |
| --- | --- |
Expand All @@ -61,7 +119,7 @@ File-local. `9` is a valid check code. `10` is reserved for
| 2 | argparse / usage |
| 3 | Mesh did not build / no UV layer |
| 4 | Base triangle count outside range |
| 5 | Material count ≠ 2 distinct slots, or wood/metal faces missing |
| 5 | Material count ≠ 3 distinct slots, or a face-count floor missed |
| 6 | UVs outside 0..1 |
| 7 | UV AABB overlap above tolerance |
| 8 | World AABB off declared outer size |
Expand All @@ -71,3 +129,8 @@ File-local. `9` is a valid check code. `10` is reserved for
| 12 | Bake did not finish or image has no data |
| 13 | Export file missing or empty |
| 14 | `--output` produced no file |
| 15 | Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, z-fight |
| 16 | Not grounded: bounding box `zmin` off 0 |
| 17 | Axe joint fit: shell count, eye clearance, engagement, breakout, bury, inset |
| 18 | Contact fit: hoop bite band, or haft fouling the log |
| 19 | Log out of plumb, or off its stated real-world size |
Loading
Loading