Skip to content

Commit 5a6fbee

Browse files
authored
feat: add vertex-weight-limit example witnessing the 4-influence skinning cap (#77)
Game engines cap skinning at four bone influences per vertex, and AI-generated rigging code violates it silently or reaches for the context-heavy bpy.ops path. This example authors deliberately rich five-bone boot weights on a mech arm, enforces the cap through the data API (top-four keep, VertexGroup.remove, renormalize), and proves the armature modifier is still exact linear blend skinning read back from the mesh's own deform layer — the weights on the mesh are the contract. The vertex-group API is stable across 4.5 LTS and 5.1; the example produces identical measured values on both. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
1 parent 8e4e857 commit 5a6fbee

12 files changed

Lines changed: 1224 additions & 3 deletions

File tree

.cursor-plugin/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"examples/text-version-stamp",
8383
"examples/turntable",
8484
"examples/uv-layer-grid",
85+
"examples/vertex-weight-limit",
8586
"examples/vse-cut-list",
8687
"examples/wave-displace"
8788
]

.github/workflows/blender-smoke.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,14 @@ jobs:
393393
# (bbox) preservation within 1e-3. Exits non-zero on failure.
394394
xvfb-run -a "$BLENDER" --background \
395395
--python examples/lod-decimate-chain/lod_decimate_chain.py --
396+
397+
- name: Shipped example - vertex weight limit (4-influence cap + LBS)
398+
run: |
399+
set -euo pipefail
400+
# Check only (no render): a five-bone mech arm with rich 5-influence
401+
# boots, pruned to the 4-influence engine cap via the data API;
402+
# asserts pre/post-limit influence counts, unit weight sums, pose
403+
# preservation (pre vs post pruning), exact LBS from the mesh's own
404+
# deform layer, and a pinned Root mount. Exits non-zero on failure.
405+
xvfb-run -a "$BLENDER" --background \
406+
--python examples/vertex-weight-limit/vertex_weight_limit.py --

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</p>
1919

2020
<p align="center">
21-
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>25 examples</strong>
21+
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>26 examples</strong>
2222
</p>
2323

2424
<p align="center">
@@ -36,7 +36,7 @@
3636

3737
## Overview
3838

39-
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 25 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
39+
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.
4040

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

@@ -466,7 +466,7 @@ round-trips through the raw `POINT` buffer.
466466
</details>
467467

468468
<details>
469-
<summary><strong>Game asset pipeline</strong> — 2 examples</summary>
469+
<summary><strong>Game asset pipeline</strong> — 3 examples</summary>
470470

471471
<table>
472472
<tr>
@@ -502,6 +502,23 @@ keeps its closed-form counts), each LOD's evaluated triangle count lands within
502502
survives within 1e-3 — with the aggressive-ratio nose-tip collapse documented
503503
as the caught failure mode.
504504

505+
</td>
506+
</tr>
507+
<tr>
508+
<td width="46%" valign="middle">
509+
<a href="examples/vertex-weight-limit/"><img src="examples/vertex-weight-limit/preview.webp" alt="Vertex weight limit: a posed mech arm on a dark studio floor - orange armor shells, black ribbed elbow boot, glowing teal forearm ring, claw gripper - deformed by weights pruned to the four-influence engine cap" /></a>
510+
</td>
511+
<td valign="middle">
512+
513+
### [vertex-weight-limit](examples/vertex-weight-limit/)
514+
515+
The game-engine max-four-bone-influences constraint, enforced through the data
516+
API (`v.groups` + `VertexGroup.remove` + renormalize) rather than the
517+
`bpy.ops.object.vertex_group_limit_total` context path. Asserts the pre-limit
518+
boots really carry five influences, no vertex ends over the cap, weights still
519+
sum to one, the pose survives pruning, and the modifier is still exact linear
520+
blend skinning read back from the mesh's own deform layer.
521+
505522
</td>
506523
</tr>
507524
</table>

ROADMAP.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
105105
- ~~VSE sequences-to-strips witness~~ **SHIPPED** as `examples/vse-cut-list/``.sequences` removed on 5.x (4.5 bridges to `.strips`), `new_effect` end kwarg `frame_end=` (4.5) vs `length=` (5.x), `frame_final_*` deprecated in favor of `left_handle`/`right_handle`/`duration`, TRANSFORM effect type removed, GAMMA_CROSS clamps to the source overlap, effect strips consume inputs only when stacked above them, same-scene scene strips render transparent; save/reload round-trip + tiny-render pixel witness
106106
- ~~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
107107
- ~~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
108+
- ~~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
109+
- 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
110+
- 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
111+
- 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)
108112
- 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`
109113
- Falsy `bpy_prop_collection` trap snippet: an empty collection is falsy, so `editor.strips or editor.sequences` silently falls through to the legacy accessor on an empty timeline — always branch on `hasattr`; likely generalizes across the API (found authoring `vse-cut-list`)
110114

10.7 KB
Loading
35.7 KB
Loading

docs/gallery/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,17 @@ <h2><a href="lod-decimate-chain/">lod-decimate-chain</a></h2>
480480
<a class="card-link" href="lod-decimate-chain/">View example <span aria-hidden="true">&rarr;</span></a>
481481
</div>
482482
</article>
483+
<article class="card" data-tags="armature mesh">
484+
<a class="card-media" href="vertex-weight-limit/" aria-label="vertex-weight-limit example detail page">
485+
<img src="assets/vertex-weight-limit-hero.webp" alt="vertex-weight-limit — A rigged mech arm pruned to the game-engine cap of four bone influences per vertex, through the data API." loading="lazy" decoding="async" />
486+
</a>
487+
<div class="card-body">
488+
<h2><a href="vertex-weight-limit/">vertex-weight-limit</a></h2>
489+
<p class="teaches">A rigged mech arm pruned to the game-engine cap of four bone influences per vertex, through the data API. The check proves no vertex exceeds the cap, weights still sum to one, the pose survives pruning, and the modifier is still exact linear blend skinning.</p>
490+
<p class="witnesses"><span class="tag">witnesses</span> Limit-total and normalize without bpy.ops: keep the top four groups, VertexGroup.remove the rest, renormalize. LBS is verified against the weights read back from the mesh&#x27;s own deform layer, not the authoring function.</p>
491+
<a class="card-link" href="vertex-weight-limit/">View example <span aria-hidden="true">&rarr;</span></a>
492+
</div>
493+
</article>
483494
</div>
484495
</main>
485496
<footer>

0 commit comments

Comments
 (0)