diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 7382844..f24c4b9 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -80,6 +80,7 @@ "examples/swatch-grid", "examples/temp-override-join", "examples/text-version-stamp", + "examples/triangulate-tangents", "examples/turntable", "examples/uv-layer-grid", "examples/vertex-weight-limit", diff --git a/.github/workflows/blender-smoke.yml b/.github/workflows/blender-smoke.yml index 86d7bf5..13ad5ca 100644 --- a/.github/workflows/blender-smoke.yml +++ b/.github/workflows/blender-smoke.yml @@ -404,3 +404,15 @@ jobs: # deform layer, and a pinned Root mount. Exits non-zero on failure. xvfb-run -a "$BLENDER" --background \ --python examples/vertex-weight-limit/vertex_weight_limit.py -- + + - name: Shipped example - triangulate + tangents (mikktspace contract) + run: | + set -euo pipefail + # Check only (no render): a lathed buckler with polar/strip/planar + # UV charts; asserts closed-form loop-triangle count, unit and + # orthogonal tangent frames, bitangent == sign*(n x t), tangents + # matching the independent edge/UV-delta formula on smooth-field + # triangles, zero flips off seams, and the re-fetched UV layer + # surviving calc_tangents. Exits non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/triangulate-tangents/triangulate_tangents.py -- diff --git a/README.md b/README.md index ab4aaac..9fcfdda 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@

- 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  26 examples + 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  27 examples

@@ -36,7 +36,7 @@ ## Overview -This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 26 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support. +This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 27 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support. The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capable client) when working on Blender add-ons, geometry nodes scripts, batch pipelines, or animation tooling. There is no build step. Edit the markdown and Python files directly. @@ -472,7 +472,7 @@ round-trips through the raw `POINT` buffer.

-Game asset pipeline — 3 examples +Game asset pipeline — 4 examples @@ -525,6 +525,23 @@ boots really carry five influences, no vertex ends over the cap, weights still sum to one, the pose survives pruning, and the modifier is still exact linear blend skinning read back from the mesh's own deform layer. + + + + +
+Triangulate and tangents: a machined steel buckler on a dark cradle - brushed grooves circulating the boss with an anisotropic sweep - proving the mikktspace tangent field a normal map depends on + + +### [triangulate-tangents](examples/triangulate-tangents/) + +The normal-mapping tangent-space contract: deterministic triangulation +(`calc_tangents` aborts on any ngon), unit orthogonal tangent frames, +`bitangent == sign * (n x t)`, and mikktspace matching the independent +edge/UV-delta formula on smooth fields. Documents the planar-on-cylinder UV +degeneracy (tangent collapses onto the normal) and the stale layer-handle +hazard that silently corrupts measurements on 4.5. +
diff --git a/ROADMAP.md b/ROADMAP.md index f4ffecd..bc03253 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -106,7 +106,8 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo - ~~glTF export round-trip witness~~ **SHIPPED** as `examples/gltf-export-roundtrip/` — `export_yup` bakes `(x,y,z)→(x,z,−y)` into vertex data with no node rotation (probed identical on 4.5.11 and 5.1.2), `export_apply` ships the evaluated mesh (one disk vertex per evaluated loop), TEXCOORD_0 is V-flipped on disk, per-triangle material bindings survive; exporter/importer RNA signatures byte-identical between 4.5.11 and 5.1.2 (guarded against future renames); `Mesh.calc_normals()` removal on 5.x surfaced during authoring - ~~LOD decimate chain witness~~ **SHIPPED** as `examples/lod-decimate-chain/` — Decimate COLLAPSE evaluated through the depsgraph is non-destructive (obj.data keeps closed-form counts), evaluated tris hit `ratio × base` within 5% (measured 0.0–0.44%), silhouette bbox survives within 1e-3 (measured 7.7e-6); a stacked Decimate halves the effective ratio, an aggressive 0.02 ratio collapses the nose tip — both caught failure modes - ~~Vertex weight limit witness~~ **SHIPPED** as `examples/vertex-weight-limit/` — the 4-influence engine cap enforced via the data API (`v.groups` + `VertexGroup.remove` + renormalize); unit sums (measured 3e-8), pose preserved (4.9e-3), exact LBS from the mesh's own deform layer (2.7e-7), Root mount pinned -- Triangulate + tangent-space witness: `mesh.calc_loop_triangles` + `calc_tangents` producing the deterministic loop topology and orthonormal tangent basis a game engine's normal mapping depends on — the fourth direction of the game-pipeline arc, identified but not built +- ~~Triangulate + tangent-space witness~~ **SHIPPED** as `examples/triangulate-tangents/` — `calc_tangents` aborts on any ngon (back cap must be an explicit fan); mikktspace matches the edge/UV-delta formula within welding tolerance on smooth fields (2.3e-6 measured); planar UVs on a cylindrical wall collapse tangents onto normals (dot 0.998); `MeshUVLoopLayer` handle dangles across `calc_tangents()` on 4.5 (471 phantom flips, silent exit 0) while the mikktspace math is byte-identical on 4.5.11 and 5.1.2 +- UV-handle lifetime snippet: re-fetch attribute/UV layers by name after any CustomData-reallocating call (`calc_tangents`, `VertexGroup.add`, modifier edits) — held handles dangle silently on 4.5, survive by luck on 5.1 (found authoring `triangulate-tangents`) - glTF skinned-mesh export witness: follow-up to `gltf-export-roundtrip` + `vertex-weight-limit` — export a rigged mesh (weights + joints) to glTF and round-trip the skin, asserting influence counts and joint bindings survive the format - Degenerate-bevel weld hazard (snippet or rule): bevel width ≥ half a box dimension creates zero-area faces whose loops weld on glTF export (found authoring `gltf-export-roundtrip`, where the count check caught a 36-vertex weld) - GAMMA_CROSS blend-curve witness: the cross blend is not the naive linear mix (mid-cross measured (0.341, 0.349, 0.463) from crimson (0.85, 0.10, 0.22) and teal (0.06, 0.75, 0.80)) — derive and assert the gamma-space closed form per frame; follow-up to `vse-cut-list` diff --git a/docs/gallery/assets/triangulate-tangents-hero.webp b/docs/gallery/assets/triangulate-tangents-hero.webp new file mode 100644 index 0000000..58d434e Binary files /dev/null and b/docs/gallery/assets/triangulate-tangents-hero.webp differ diff --git a/docs/gallery/contact-sheets/triangulate-tangents-contact-sheet.webp b/docs/gallery/contact-sheets/triangulate-tangents-contact-sheet.webp new file mode 100644 index 0000000..d1eb493 Binary files /dev/null and b/docs/gallery/contact-sheets/triangulate-tangents-contact-sheet.webp differ diff --git a/docs/gallery/index.html b/docs/gallery/index.html index ab02d70..1193f72 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -491,6 +491,17 @@

vertex-weight-limit

View example +
+ + triangulate-tangents — A machined buckler verifying the tangent-space contract a game engine's normal mapping depends on. + +
+

triangulate-tangents

+

A machined buckler verifying the tangent-space contract a game engine's normal mapping depends on. Deterministic triangulation, unit orthogonal tangent frames, and the edge/UV-delta formula matching mikktspace within welding tolerance.

+

witnesses calc_tangents aborts on any ngon; planar UVs on a cylindrical wall collapse tangents onto normals; a MeshUVLoopLayer handle held across calc_tangents dangles on 4.5 (471 phantom flips, silent exit 0) while the mikktspace math is byte-identical on both versions.

+ View example +
+