Skip to content

Commit be21ded

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): redesign custom-normals-shade jerry can and make the three shadings read
The gallery review judged the hero weak: three near-identical olive cans whose shading differences vanished at card size, and a crude X of floater prisms as the only detail. PR #257 demoted the can from the asset reference set as least-designed. The can is rebuilt as a pressed-steel prop. The shell has four raised rounded-triangle panels on each face with 45-deg walls, and the X is the channel left between them. There is also a weld-seam bead, a triple handle on welded feet, and a spout with red seal, domed cap, cam lever, hinge and locking pin. Olive paint chips to steel on exposed edges, driven by a `wear` point attribute on coplanar support rings. The facet angles are chosen for the contract: curves step at 22.5 deg or less, creases break at 45 or 90 deg, and nothing sits near 30 deg. The render shows three fresh builds, flat, smooth-everywhere and by-angle. The by-angle can's `sharp_edge` set is read back from the mesh and traced in thin cyan lines. A low strip light makes the highlights diverge. The render path now runs the gallery_framing (exit 10) and gallery_asset_quality (exit 11) gates it previously skipped. Checks are unchanged in tolerance. by-angle now covers Shell, Handle and Neck (1304 of 4738 edges, an exact match). The custom-normal round-trip request pattern moved from an index sweep to a 20-deg tilt toward each corner's bisector. On the new 8k-loop shell the old sweep measured Blender's lnor-encoder fan merge (1.38e-02), and a fixed 144-direction cycle hit its in-plane angle snap (9.1e-03). Both come from the 0.81-deg LNOR_SPACE_TRIGO_THRESHOLD, not from storage precision. The new pattern stays clear of both and reads back at 3.904e-05 (tol 2e-4). Long straight runs are split so the face fill has no slivers. Signed-off-by: TMHSDigital <[email protected]> Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent 08e1acc commit be21ded

10 files changed

Lines changed: 1107 additions & 378 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ concave grooves are free.
971971
</tr>
972972
<tr>
973973
<td width="46%" valign="middle">
974-
<a href="examples/custom-normals-shade/"><img src="examples/custom-normals-shade/preview.webp" alt="Custom normals and shade by angle: three olive-drab jerry can props with pressed X ribs and red spout rings on a dark studio floor - one faceted flat, one smeared by smooth-everything, one crisp with correct hard edges - proving the post-4.1 shading contract" /></a>
974+
<a href="examples/custom-normals-shade/"><img src="examples/custom-normals-shade/preview.webp" alt="Custom normals and shade by angle: three olive-drab jerry cans with X-pressed panels, triple handles and red-sealed spouts on a dark studio floor - one faceted flat, one smeared glossy by smooth-everything, one crisp by-angle with its sharp edges traced in thin cyan lines - proving the post-4.1 shading contract" /></a>
975975
</td>
976976
<td valign="middle">
977977

@@ -984,7 +984,7 @@ on **both** 4.5 LTS and 5.1. `set_sharp_from_angle` marks sharp exactly the
984984
edges an independent dihedral recompute predicts; evaluated loop normals
985985
weld across smooth edges and split by the dihedral across sharp ones;
986986
custom split normals survive depsgraph evaluation within their int16
987-
quantization (1.407e-04, not float-exact). Documents the legacy
987+
quantization (3.904e-05, not float-exact). Documents the legacy
988988
`shade_auto_smooth` operator trap: CANCELLED headless on 4.5, FINISHED
989989
with the Smooth-by-Angle modifier on 5.1.
990990

51.7 KB
Loading
27.7 KB
Loading
-4.12 KB
Loading

‎docs/gallery/custom-normals-shade/index.html‎

Lines changed: 536 additions & 181 deletions
Large diffs are not rendered by default.

‎docs/gallery/index.html‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,12 +706,12 @@ <h2><a href="collision-hull-proxy/">collision-hull-proxy</a></h2>
706706
</article>
707707
<article class="card" data-tags="mesh attributes">
708708
<a class="card-media" href="custom-normals-shade/" tabindex="-1" aria-hidden="true">
709-
<img src="assets/custom-normals-shade-hero.webp" alt="Three olive jerry cans with embossed X ribs, shaded flat, smooth and smooth-by-angle." loading="lazy" decoding="async" />
709+
<img src="assets/custom-normals-shade-hero.webp" alt="Three olive jerry cans with X-pressed panels, triple handles and red-sealed spouts: faceted flat, glossy-smeared smooth-everywhere, and crisp by-angle with its sharp edges traced in thin cyan lines." loading="lazy" decoding="async" />
710710
</a>
711711
<div class="card-body">
712712
<h2><a href="custom-normals-shade/">custom-normals-shade</a></h2>
713-
<p class="teaches">A jerry can prop shaded three ways to prove the post-4.1 shading contract: hard edges are mesh data, landing exactly where the dihedral crosses. Face smooth flags plus a sharp_edge attribute, verified against an independently recomputed dihedral test, and per-loop custom normals surviving depsgraph evaluation within their int16 storage quantization (1.407e-04, not float-exact).</p>
714-
<p class="witnesses"><span class="tag">witnesses</span> use_auto_smooth, use_custom_normals and calc_normals are AttributeError on BOTH 4.5 LTS and 5.1 - AI code still emits them. The legacy shade_auto_smooth operator CANCELS headless on 4.5 (asset load never finishes; mesh untouched; no exception) while 5.1 FINISHES with the Smooth by Angle NODES modifier. By-angle sharp sets match the independent dihedral recompute exactly (188 of 388 edges over 3 meshes).</p>
713+
<p class="teaches">A jerry can prop shaded three ways to prove the post-4.1 shading contract: hard edges are mesh data, landing exactly where the dihedral crosses. Face smooth flags plus a sharp_edge attribute, verified against an independently recomputed dihedral test, and per-loop custom normals surviving depsgraph evaluation within their int16 storage quantization (3.904e-05 over 8196 loops, not float-exact).</p>
714+
<p class="witnesses"><span class="tag">witnesses</span> use_auto_smooth, use_custom_normals and calc_normals are AttributeError on BOTH 4.5 LTS and 5.1 - AI code still emits them. The legacy shade_auto_smooth operator CANCELS headless on 4.5 (asset load never finishes; mesh untouched; no exception) while 5.1 FINISHES with the Smooth by Angle NODES modifier. By-angle sharp sets match the independent dihedral recompute exactly (1304 of 4738 edges over 3 meshes).</p>
715715
<a class="card-link" href="custom-normals-shade/">View example<span class="sr-only"> custom-normals-shade</span> <span aria-hidden="true">&rarr;</span></a>
716716
</div>
717717
</article>

‎examples/custom-normals-shade/README.md‎

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Custom Normals + Shade by Angle
22

3-
A runnable example that builds a jerry can prop — rounded-slab shell,
4-
pressed X ribs, spout and cap, three-post handle — and verifies the shading
3+
A runnable example that builds a jerry can prop — a pressed-steel shell
4+
whose front and back carry four raised rounded-triangle panels (the X is
5+
the channel between them), a weld-seam bead round the side band, a triple
6+
carry handle on welded feet, and a spout with red seal, domed cap, cam
7+
lever, hinge and locking pin; olive paint chipped to steel on exposed edges
8+
by a `wear` point attribute — and verifies the shading
59
contract a game prop's silhouette depends on: which edges read hard and
610
which read smooth is mesh DATA, carried since Blender 4.1 by face smooth
711
flags plus a `sharp_edge` attribute,
@@ -28,18 +32,25 @@ It is not an engine exporter and does not claim engine/FiveM compatibility
2832
- **Shade-by-angle is exact.** `mesh.set_sharp_from_angle(30°)` (which also
2933
sets the face smooth flags — probed on both versions) marks sharp exactly
3034
the edges whose *independently recomputed* dihedral angle crosses the
31-
threshold: Shell 48 of 72 edges, rib 12 of 12, neck 128 of 304 — an exact
32-
set match, not a count approximation.
35+
threshold: Shell 1144 of 4098 edges, Handle 32 of 368, Neck 128 of 272
36+
(1304 of 4738) — an exact set match, not a count approximation.
3337
- **The evaluated shading matches the attribute's promise.** Through
3438
depsgraph evaluation, loop normals across a smooth edge are welded
35-
(deviation 0.0, tol 1e-3) and loop normals across a sharp edge carry
36-
their face normals split by exactly the dihedral (angle error 0.0 rad,
37-
tol 5e-3), all unit length (err 5.6e-08).
39+
(deviation 0.0, tol 1e-3) and loop normals across a sharp edge are split
40+
by the dihedral (angle error 1.898e-03 rad, tol 5e-3 — the curved side
41+
band and the panels' rounded wall corners average a fan whose facets
42+
step at most 22.5 and 10 deg), all unit length (err 1.0e-07).
3843
- **Custom split normals survive depsgraph evaluation** — with a
3944
quantization budget, not float precision: `normals_split_custom_set`
40-
stores per-loop normals in int16, so a 144-loop round-trip reads back
41-
within **1.407e-04** (tol 2e-4), unit length within 1.1e-07. Asserting
42-
float-exact custom normals is a real bug this check catches.
45+
stores per-loop normals in int16, so an 8196-loop round-trip reads back
46+
within **3.904e-05** (tol 2e-4), unit length within 1.7e-07. Asserting
47+
float-exact custom normals is a real bug this check catches. Each corner
48+
requests its face normal tilted 20 deg toward the corner bisector. The
49+
pattern matters: Blender's lnor encoder merges requests within one fan
50+
that sit closer than ~0.81 deg and snaps an in-plane angle under 0.81 deg
51+
to zero. On this shell an index-swept pattern measured the merge
52+
(1.38e-02) and a 144-direction cycle hit the snap on 13 corners
53+
(9.1e-03). Neither is storage precision, so the check keeps clear of both.
4354
- **The divergence: the legacy `shade_auto_smooth` OPERATOR is a
4455
version-split trap.** It builds the Smooth-by-Angle node-group modifier
4556
from a bundled asset. Headless on **4.5 LTS the asset load never
@@ -50,22 +61,28 @@ It is not an engine exporter and does not claim engine/FiveM compatibility
5061
portable path is the data API above, version-gated here explicitly.
5162

5263
**What each check catches on failure:** author/audit threshold drift
53-
(probe: sharp marks applied at 20° but audited at 30°, exit 5, 4 extra
54-
edges); the two halves of the contract out of sync (probe: `sharp_edge`
55-
set but face smooth flags lost, exit 6, smooth-edge loops split by
56-
3.83e-01); float-exactness assumed of custom normals (probe: tolerance
57-
1e-6, exit 7, measured 1.407e-04); and any future version that resurrects
58-
the legacy API or changes the operator's headless behavior (exit 3/8).
59-
60-
**Version witness:** every value above is identical on Blender 4.5.11 LTS
61-
and 5.1.2 except the `shade_auto_smooth` operator behavior, which is
64+
(probe: sharp marks applied at 20° but audited at 30°, exit 5, 48 extra
65+
edges on the shell); the two halves of the contract out of sync (probe:
66+
`sharp_edge` set but face smooth flags lost, exit 6, smooth-edge loops
67+
split by 3.902e-01); float-exactness assumed of custom normals (probe:
68+
tolerance 1e-6, exit 7, measured 3.904e-05); and any future version that
69+
resurrects the legacy API or changes the operator's headless behavior
70+
(exit 3/8). The render path adds the Layer 1 gates: framing (exit 10) and
71+
asset quality (exit 11).
72+
73+
**Version witness:** every value above is identical on Blender 4.5.11 LTS,
74+
5.1.2 and 5.2.1 LTS except the `shade_auto_smooth` operator behavior, which is
6275
asserted per version (CANCELLED + untouched on 4.5, FINISHED + NODES
63-
modifier on 5.1).
76+
modifier on 5.1 and 5.2).
6477

65-
The render shows the same can shaded three ways — flat (faceted corners),
66-
smooth-everywhere (the smeared AI bug: ribs melt, highlights warp at the
67-
rim), and by-angle (the contract: smooth walls, crisp edges) — under a
68-
strip light whose reflection exposes every normal discontinuity.
78+
The render shows three fresh builds of the same can, one per shading
79+
treatment. Left, flat: the side-band corners, cap and handle break into
80+
facets. Middle, smooth-everywhere (the shade-smooth-and-forget AI habit):
81+
the flat fields smear into gradients and the pressed panels go pillowy.
82+
Right, by-angle (the contract): crisp creases and smooth rounds, with the
83+
`sharp_edge` attribute read back from the mesh and traced as thin cyan
84+
lines, so the viewer sees exactly which edges the data marked hard. A
85+
low strip light makes the highlight shapes diverge between the three.
6986

7087
## Run
7188

@@ -98,6 +115,8 @@ against it.
98115
| 7 | Custom split normals lost or dequantized in evaluation |
99116
| 8 | `shade_auto_smooth` operator behavior drifted from the version split |
100117
| 9 | `--output` produced no file |
118+
| 10 | Render path: Layer 1 framing violation (`gallery_framing`) |
119+
| 11 | Render path: asset-quality floor violation (`gallery_asset_quality`) |
101120

102121
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
103122
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).

0 commit comments

Comments
 (0)