From 561f92000e2bc32f36e06d84b9bd70bedf282351 Mon Sep 17 00:00:00 2001 From: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:22:14 -0400 Subject: [PATCH] fix: hang the iron cauldron inside the tripod Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor --- docs/gallery/assets/iron-cauldron-hero.webp | Bin 10582 -> 10776 bytes docs/gallery/index.html | 2 +- docs/gallery/iron-cauldron/index.html | 66 +++++++++++++++++--- showcase/gallery.json | 2 +- showcase/iron-cauldron/README.md | 17 ++--- showcase/iron-cauldron/iron_cauldron.py | 60 ++++++++++++++++-- showcase/iron-cauldron/preview.webp | Bin 9662 -> 9732 bytes 7 files changed, 127 insertions(+), 20 deletions(-) diff --git a/docs/gallery/assets/iron-cauldron-hero.webp b/docs/gallery/assets/iron-cauldron-hero.webp index c17a5b852fc2b819183e1e8627b17b8b88d9f890..c455fc3cb20ce7317703bb108877e2573dc1c84f 100644 GIT binary patch literal 10776 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 diff --git a/docs/gallery/index.html b/docs/gallery/index.html index 32315c8..2642789 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -1145,7 +1145,7 @@

tavern-stool

iron-cauldron

A procedural hanging iron cauldron with a bulged open pot, bail, and timber tripod through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

-

witnesses Recomputed: 3388 tris, two materials with 450 wood and 1198 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.606×0.591×0.826 m, LOD ratios in band (5.2 COLLAPSE 4 tris leaner on LOD2), convex collider 180 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.

+

witnesses Recomputed: 3388 tris, two materials with 450 wood and 1198 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.013×0.986×0.827 m, pot–leg clearance 0.051 m, LOD ratios in band (5.2 COLLAPSE 8 tris leaner on LOD2), convex collider 98 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.

View example
diff --git a/docs/gallery/iron-cauldron/index.html b/docs/gallery/iron-cauldron/index.html index e7fb5c5..239ed7f 100644 --- a/docs/gallery/iron-cauldron/index.html +++ b/docs/gallery/iron-cauldron/index.html @@ -253,7 +253,7 @@

iron-cauldron

iron-cauldron render

Rendered headless by the example itself — click to zoom.

-
witnesses Recomputed: 3388 tris, two materials with 450 wood and 1198 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.606×0.591×0.826 m, LOD ratios in band (5.2 COLLAPSE 4 tris leaner on LOD2), convex collider 180 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.
+
witnesses Recomputed: 3388 tris, two materials with 450 wood and 1198 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.013×0.986×0.827 m, pot–leg clearance 0.051 m, LOD ratios in band (5.2 COLLAPSE 8 tris leaner on LOD2), convex collider 98 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.
blender --background --python showcase/iron-cauldron/iron_cauldron.py --
@@ -264,7 +264,7 @@

iron-cauldron

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.

-

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | | Base triangles | 3280–3520 | 3388 / 3388 / 3388 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | | LOD2 ratio | 0.10–0.35 of base | 0.2196 / 0.2196 / 0.2184 | | Materials | exactly 2 distinct, ≥12 wood, ≥24 metal | 2 slots, 450 wood, 1198 metal | | UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 | | Outer AABB | (0.606, 0.591, 0.826) m ± 0.01 | (0.6061, 0.5911, 0.8255), zmin 0.002 | | Collider tris | ≤ 200 | 180 | | Export | written, size > 0 | 250748 / 250748 / 250736 bytes |

+

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | | Base triangles | 3280–3520 | 3388 / 3388 / 3388 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | | LOD2 ratio | 0.10–0.35 of base | 0.2196 / 0.2196 / 0.2172 | | Materials | exactly 2 distinct, ≥12 wood, ≥24 metal | 2 slots, 450 wood, 1198 metal | | UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 | | Outer AABB | (1.013, 0.986, 0.827) m ± 0.01 | (1.0128, 0.9865, 0.8271), zmin 0.002 | | Pot–leg clearance | ≥ 0.04 m (recomputed vs pole axes) | 0.0513 / 0.0513 / 0.0513 | | Collider tris | ≤ 200 | 98 | | Export | written, size > 0 | 245060 / 245060 / 245044 bytes |

DECIMATE COLLAPSE triangle counts are not identical across series — 5.2.1 is 4 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 12 B on 5.2.1 (glTF serializer), not a gated axis.

--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.

Run

@@ -274,7 +274,7 @@

Run

Smoke does not pass --output or --skip-decimate.

Exit codes

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path.

-

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 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 | | 6 | UVs outside 0..1 | | 7 | UV AABB overlap above tolerance | | 8 | World AABB off declared outer size | | 9 | LOD ratio band (--skip-decimate lands here) | | 10 | Framing gate (render path only) | | 11 | Collider triangle count above ceiling | | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | --output produced no file |

+

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | | 3 | Mesh did not build / no UV layer / pot clips tripod | | 4 | Base triangle count outside range | | 5 | Material count ≠ 2 distinct slots, or wood/metal faces missing | | 6 | UVs outside 0..1 | | 7 | UV AABB overlap above tolerance | | 8 | World AABB off declared outer size | | 9 | LOD ratio band (--skip-decimate lands here) | | 10 | Framing gate (render path only) | | 11 | Collider triangle count above ceiling | | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | --output produced no file |

Source

@@ -330,12 +330,13 @@

Source

R_MID = 0.205 R_TOP = 0.148 APEX_Z = 0.82 -TRIPOD_R = 0.32 +TRIPOD_R = 0.56 BAIL_SEGS = 12 BAIL_R = 0.009 BBOX_TOL = 0.01 +POT_LEG_CLEARANCE_MIN = 0.04 # Fitted after locking geometry. Recomputed from bound_box. -OUTER_SIZE = (0.606, 0.591, 0.826) +OUTER_SIZE = (1.013, 0.986, 0.827) BASE_TRIS_MIN = 3280 BASE_TRIS_MAX = 3520 @@ -726,6 +727,49 @@

Source

return (min(xs), min(ys), min(zs), max(xs), max(ys), max(zs)) +def pot_tripod_clearance(mesh): + # Recomputed from verts vs the three pole axes. Leg verts sit on the + # axes and are skipped; remaining shell verts in the pot band must + # clear the pole radius. + feet = [] + for i in range(3): + ang = i * (2.0 * math.pi / 3.0) + math.radians(18.0) + feet.append( + Vector((TRIPOD_R * math.cos(ang), TRIPOD_R * math.sin(ang), 0.016)) + ) + apex = Vector((0.0, 0.0, APEX_Z)) + z0 = POT_Z0 - 0.02 + z1 = POT_Z0 + POT_H + 0.03 + min_c = None + for v in mesh.vertices: + p = Vector(v.co) + if p.z < z0 or p.z > z1: + continue + if math.hypot(p.x, p.y) < 0.06: + continue + nearest = None + on_pole = False + for foot in feet: + ab = apex - foot + denom = ab.length_squared + if denom < 1e-12: + continue + t = max(0.0, min(1.0, (p - foot).dot(ab) / denom)) + axis = (p - (foot + t * ab)).length + rad = 0.028 * (1.0 - t) + 0.018 * t + gap = axis - rad + if axis < rad + 0.006: + on_pole = True + break + if nearest is None or gap < nearest: + nearest = gap + if on_pole or nearest is None: + continue + if min_c is None or nearest < min_c: + min_c = nearest + return min_c if min_c is not None else -1.0 + + def uv_stats(mesh): uv = mesh.uv_layers.active if uv is None: @@ -868,6 +912,14 @@

Source

size_x = bb[3] - bb[0] size_y = bb[4] - bb[1] size_z = bb[5] - bb[2] + clearance = pot_tripod_clearance(low.data) + print(f"measured pot_tripod_clearance={clearance:.4f}") + if clearance < POT_LEG_CLEARANCE_MIN: + return fail( + f"pot clips tripod clearance {clearance:.4f} " + f"< {POT_LEG_CLEARANCE_MIN}", + 3, + ), None, None, None, None, None img, tex = setup_bake_image(low, wood) if img is None: @@ -1046,10 +1098,10 @@

Source

cam_data = bpy.data.cameras.new("Cam") cam_data.lens = 50.0 cam = bpy.data.objects.new("Cam", cam_data) - cam.location = (1.58, -2.18, 1.22) + cam.location = (1.82, -2.50, 1.40) scene.collection.objects.link(cam) aim = bpy.data.objects.new("Aim", None) - aim.location = (0.0, 0.0, 0.46) + aim.location = (0.0, 0.0, 0.40) scene.collection.objects.link(aim) con = cam.constraints.new("TRACK_TO") con.target = aim diff --git a/showcase/gallery.json b/showcase/gallery.json index da14762..96e4d22 100644 --- a/showcase/gallery.json +++ b/showcase/gallery.json @@ -285,7 +285,7 @@ "name": "iron-cauldron", "dir": "showcase/iron-cauldron", "teaches": "A procedural hanging iron cauldron with a bulged open pot, bail, and timber tripod through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.", - "witnessesFix": "Recomputed: 3388 tris, two materials with 450 wood and 1198 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.606×0.591×0.826 m, LOD ratios in band (5.2 COLLAPSE 4 tris leaner on LOD2), convex collider 180 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.", + "witnessesFix": "Recomputed: 3388 tris, two materials with 450 wood and 1198 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.013×0.986×0.827 m, pot–leg clearance 0.051 m, LOD ratios in band (5.2 COLLAPSE 8 tris leaner on LOD2), convex collider 98 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.", "hero": "docs/gallery/assets/iron-cauldron-hero.webp", "preview": "showcase/iron-cauldron/preview.webp", "tags": [ diff --git a/showcase/iron-cauldron/README.md b/showcase/iron-cauldron/README.md index e679d67..34857e2 100644 --- a/showcase/iron-cauldron/README.md +++ b/showcase/iron-cauldron/README.md @@ -23,18 +23,21 @@ materials, UVs, evaluated LOD, collider, or export file. | --- | --- | --- | | Base triangles | 3280–3520 | 3388 / 3388 / 3388 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | -| LOD2 ratio | 0.10–0.35 of base | 0.2196 / 0.2196 / 0.2184 | +| LOD2 ratio | 0.10–0.35 of base | 0.2196 / 0.2196 / 0.2172 | | Materials | exactly 2 distinct, ≥12 wood, ≥24 metal | 2 slots, 450 wood, 1198 metal | | UVs | in `0..1`, AABB overlap ≤ 1e-5 | in range, overlap 0 | -| Outer AABB | (0.606, 0.591, 0.826) m ± 0.01 | (0.6061, 0.5911, 0.8255), zmin 0.002 | -| Collider tris | ≤ 200 | 180 | -| Export | written, size > 0 | 250748 / 250748 / 250736 bytes | +| Outer AABB | (1.013, 0.986, 0.827) m ± 0.01 | (1.0128, 0.9865, 0.8271), zmin 0.002 | +| Pot–leg clearance | ≥ 0.04 m (recomputed vs pole axes) | 0.0513 / 0.0513 / 0.0513 | +| Collider tris | ≤ 200 | 98 | +| Export | written, size > 0 | 245060 / 245060 / 245044 bytes | DECIMATE COLLAPSE triangle counts are **not** identical across series — -5.2.1 is 4 tris leaner on LOD2. The gate is a ratio band, not an exact +5.2.1 is 8 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 12 B on 5.2.1 (glTF serializer), not a gated axis. +counts differ by 16 B on 5.2.1 (glTF serializer), not a gated axis. +The pot–leg clearance is recomputed from shell verts vs the three pole +axes; clipping exits 3. `--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. @@ -59,7 +62,7 @@ File-local. `9` is a valid check code. `10` is reserved for | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | -| 3 | Mesh did not build / no UV layer | +| 3 | Mesh did not build / no UV layer / pot clips tripod | | 4 | Base triangle count outside range | | 5 | Material count ≠ 2 distinct slots, or wood/metal faces missing | | 6 | UVs outside 0..1 | diff --git a/showcase/iron-cauldron/iron_cauldron.py b/showcase/iron-cauldron/iron_cauldron.py index 5779151..5f7cf5c 100644 --- a/showcase/iron-cauldron/iron_cauldron.py +++ b/showcase/iron-cauldron/iron_cauldron.py @@ -46,12 +46,13 @@ R_MID = 0.205 R_TOP = 0.148 APEX_Z = 0.82 -TRIPOD_R = 0.32 +TRIPOD_R = 0.56 BAIL_SEGS = 12 BAIL_R = 0.009 BBOX_TOL = 0.01 +POT_LEG_CLEARANCE_MIN = 0.04 # Fitted after locking geometry. Recomputed from bound_box. -OUTER_SIZE = (0.606, 0.591, 0.826) +OUTER_SIZE = (1.013, 0.986, 0.827) BASE_TRIS_MIN = 3280 BASE_TRIS_MAX = 3520 @@ -442,6 +443,49 @@ def world_bbox(obj): return (min(xs), min(ys), min(zs), max(xs), max(ys), max(zs)) +def pot_tripod_clearance(mesh): + # Recomputed from verts vs the three pole axes. Leg verts sit on the + # axes and are skipped; remaining shell verts in the pot band must + # clear the pole radius. + feet = [] + for i in range(3): + ang = i * (2.0 * math.pi / 3.0) + math.radians(18.0) + feet.append( + Vector((TRIPOD_R * math.cos(ang), TRIPOD_R * math.sin(ang), 0.016)) + ) + apex = Vector((0.0, 0.0, APEX_Z)) + z0 = POT_Z0 - 0.02 + z1 = POT_Z0 + POT_H + 0.03 + min_c = None + for v in mesh.vertices: + p = Vector(v.co) + if p.z < z0 or p.z > z1: + continue + if math.hypot(p.x, p.y) < 0.06: + continue + nearest = None + on_pole = False + for foot in feet: + ab = apex - foot + denom = ab.length_squared + if denom < 1e-12: + continue + t = max(0.0, min(1.0, (p - foot).dot(ab) / denom)) + axis = (p - (foot + t * ab)).length + rad = 0.028 * (1.0 - t) + 0.018 * t + gap = axis - rad + if axis < rad + 0.006: + on_pole = True + break + if nearest is None or gap < nearest: + nearest = gap + if on_pole or nearest is None: + continue + if min_c is None or nearest < min_c: + min_c = nearest + return min_c if min_c is not None else -1.0 + + def uv_stats(mesh): uv = mesh.uv_layers.active if uv is None: @@ -584,6 +628,14 @@ def check(skip_decimate): size_x = bb[3] - bb[0] size_y = bb[4] - bb[1] size_z = bb[5] - bb[2] + clearance = pot_tripod_clearance(low.data) + print(f"measured pot_tripod_clearance={clearance:.4f}") + if clearance < POT_LEG_CLEARANCE_MIN: + return fail( + f"pot clips tripod clearance {clearance:.4f} " + f"< {POT_LEG_CLEARANCE_MIN}", + 3, + ), None, None, None, None, None img, tex = setup_bake_image(low, wood) if img is None: @@ -762,10 +814,10 @@ def light(name, loc, energy, size, col, rot): cam_data = bpy.data.cameras.new("Cam") cam_data.lens = 50.0 cam = bpy.data.objects.new("Cam", cam_data) - cam.location = (1.58, -2.18, 1.22) + cam.location = (1.82, -2.50, 1.40) scene.collection.objects.link(cam) aim = bpy.data.objects.new("Aim", None) - aim.location = (0.0, 0.0, 0.46) + aim.location = (0.0, 0.0, 0.40) scene.collection.objects.link(aim) con = cam.constraints.new("TRACK_TO") con.target = aim diff --git a/showcase/iron-cauldron/preview.webp b/showcase/iron-cauldron/preview.webp index 98537b2f24bf391e627f99c8309be891f65501ae..86c7d14076ae3ccd69e2492d18eacef4f5668663 100644 GIT binary patch literal 9732 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 S7R)ofDz21wli?}Wr~m+2A)_(? literal 9662 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 AUjP6A