Skip to content

feat: add custom-normals-shade example (post-4.1 shading contract)#97

Merged
TMHSDigital merged 1 commit into
mainfrom
feat/example-custom-normals-shade
Jul 22, 2026
Merged

feat: add custom-normals-shade example (post-4.1 shading contract)#97
TMHSDigital merged 1 commit into
mainfrom
feat/example-custom-normals-shade

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

Summary

Ships examples/custom-normals-shade/ — the second of the "game prop validation" pair (pairs with collision-hull-proxy, #96). A jerry can prop (rounded-slab shell, pressed X ribs, spout + cap, three-post handle) verifying the shading contract a prop's silhouette depends on: since Blender 4.1, hard/smooth edges are mesh data — face smooth flags plus a sharp_edge attribute.

Scope guard: witnesses the bpy-level contract a prop pipeline relies on; not an engine exporter, no FiveM/engine compatibility claimed — the README says so explicitly.

Contract witnessed

  • Legacy shading API is gone on both versionsuse_auto_smooth, use_custom_normals, calc_normals are AttributeError on 4.5 LTS and 5.1. AI code still emits mesh.use_auto_smooth = True; the check keeps it dead.
  • Shade-by-angle is exactmesh.set_sharp_from_angle(30°) (probed: it also sets the face smooth flags, both versions) marks sharp exactly the edges an independently recomputed dihedral test predicts: Shell 48/72, rib 12/12, neck 128/304 — exact set match, 188/388 edges.
  • Evaluated shading matches the promise — through depsgraph evaluation: loop normals welded across smooth edges (deviation 0.0, tol 1e-3), split by exactly the dihedral across sharp edges (0.0 rad err, tol 5e-3), unit length 5.6e-08.
  • Custom split normals survive depsgraph evaluation — but in int16 storage, not float: 144-loop round-trip reads back within 1.407e-04 (tol 2e-4). Asserting float-exact custom normals is a real, caught bug.
  • Version divergence (probed, asserted per version): the legacy shade_auto_smooth operator needs the bundled Smooth-by-Angle node-group asset. Headless on 4.5: "Warning: Asset loading is unfinished"{'CANCELLED'}, mesh untouched, no exception — silent flat shading for any script that ignores the return set. On 5.1: {'FINISHED'} + the NODES modifier. The data API is the portable path.

Falsification evidence (probes run, checks fail as claimed)

Probe Breakage Result
Threshold drift sharp marks applied at 20°, audited at 30° exit 5, 4 extra edges
Contract halves out of sync sharp_edge set, face smooth flags then lost exit 6, smooth-edge loops split by 3.830e-01
Float-exact custom normals tolerance tightened to 1e-6 exit 7, measured 1.407e-04

The legacy-operator divergence is asserted live on every run (exit 8 on drift) — it is the version gate, not a probe.

Visual notes

Hero render (Cycles, 48 samples): the same can three ways under a strip light — flat (faceted corners), smooth-everywhere (the smeared AI bug: ribs melt, highlight warps at the rim), by-angle (crisp). A broken path is visible in the image. Contact sheet (candidate vs pinned calibration set): custom-normals-shade-contact-sheet.webp

Per-criterion verdicts: stage darkness matches (same recipe); wedge warmth present (pool right); subject fill ~75% frame height; saturation — olive-drab is deliberately muted vs the vivid set, carried by the glossy steel + red marker rings; thumbnail legibility — the melted-vs-crisp rib tell survives 320px. Mean luminance 146.3/255 vs calibration 139.4 / 100.1 / 143.4 — top of band but within 2% of bmesh-gear; the set's own spread is 43 points, so not sortable as the odd one out. First draft measured 151.7 and was re-lit (key 600→520 W, strip 650→460 W, richer olive) before shipping. No calibration-set change.

Test plan

  • .scratch/blender-5.1.2-windows-x64/blender.exe (reports Blender 5.1.2, build 2026-05-19) — check-only exit 0; all three falsification probes confirmed; EEVEE drafts + final Cycles hero rendered locally.
  • .scratch/blender-4.5.11-windows-x64/blender.exe (reports Blender 4.5.11 LTS, build 2026-06-23) — check-only exit 0 with the CANCELLED legacy-op path asserted; all other values identical to 5.1.2.
  • CI: blender-smoke runs the check-only step under both matrix series. Note: CI currently resolves 4.5.12 (local is 4.5.11); the 4.5-series legacy-op assertion (CANCELLED headless) is expected to hold — if a 4.5.x patch changed asset loading, this step goes red and that drift is the witness working.

Live-run vs inspection

  • Proven by live run (local 5.1.2 + 4.5.11): every check value above, the shade_auto_smooth divergence (isolated fresh-mesh probes plus the in-script assertion), probe exits, hero/preview/contact-sheet pixels, gallery HTML audited post-generation.
  • Inspection only: EEVEE hero path on 4.5 (version-shared eevee_engine_id() branch exercised on 5.1; Cycles is the shipped hero engine).

Integration

Example dir + README + preview, hero webp, committed contact sheet, gallery.json entry, plugin manifest (alphabetical), blender-smoke.yml step (both matrix series), top-level README counts 31→32 + Game asset pipeline row (6→7), ROADMAP pool entry marked shipped, gallery regenerated via scripts/build_gallery.py.

A prop's silhouette lives or dies on which edges shade hard and which
shade smooth, and since Blender 4.1 that is mesh data: face smooth flags
plus a sharp_edge attribute. AI-generated code still emits the removed API
(mesh.use_auto_smooth, shade_auto_smooth), so this example pins the truth
on both supported versions: the legacy API is AttributeError on 4.5 LTS
and 5.1; set_sharp_from_angle marks sharp exactly the edges an
independent dihedral recompute predicts (188/388 across 3 meshes);
evaluated loop normals weld across smooth edges and split by the dihedral
across sharp ones (0.0 deviation); and per-loop custom normals survive
depsgraph evaluation within their int16 storage quantization (1.407e-04 —
asserting float-exactness is a real, caught bug).

Version-gated divergence, probed on both binaries: the legacy
shade_auto_smooth operator needs the bundled Smooth-by-Angle node-group
asset — headless on 4.5 it returns {'CANCELLED'} with the mesh untouched
(silent flat shading for scripts that ignore the return set), while 5.1
FINISHES with the NODES modifier. The data API is the portable path.

Falsified: threshold drift (exit 5, 4 extra edges), smooth flags lost
after marking (exit 6, 3.83e-01), float-exact custom normals (exit 7,
1.407e-04 vs 1e-6). Check output identical on Blender 4.5.11 LTS and
5.1.2 except the asserted operator divergence.

Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci labels Jul 22, 2026
@TMHSDigital
TMHSDigital merged commit 0430939 into main Jul 22, 2026
9 checks passed
@TMHSDigital
TMHSDigital deleted the feat/example-custom-normals-shade branch July 22, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant