diff --git a/docs/gallery/assets/vertex-weight-limit-hero.webp b/docs/gallery/assets/vertex-weight-limit-hero.webp index fbd5b3e..1316a1f 100644 Binary files a/docs/gallery/assets/vertex-weight-limit-hero.webp and b/docs/gallery/assets/vertex-weight-limit-hero.webp differ diff --git a/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp b/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp index 576fc13..cd155d0 100644 Binary files a/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp and b/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp differ diff --git a/docs/gallery/vertex-weight-limit/index.html b/docs/gallery/vertex-weight-limit/index.html index bb3469b..b0a94d7 100644 --- a/docs/gallery/vertex-weight-limit/index.html +++ b/docs/gallery/vertex-weight-limit/index.html @@ -183,12 +183,12 @@

vertex-weight-limit

-

A runnable example that rigs a mech arm — bolted pedestal and shoulder fairing, an upper arm ending in clevis cheeks, the elbow hinge pin capped through a ribbed flex bellows, a long plated forearm, wrist bellows and collar, and a three-finger gripper — with deliberately rich five-bone weight bumps in the bellows, then enforces the game-engine maximum of four bone influences per vertex through the data API, following mesh-editing-and-bmesh and building on the linear-blend-skinning precedent of armature-bend.

+

A runnable example that rigs a mech arm — bolted pedestal and shoulder fairing, an upper arm ending in clevis cheeks, the elbow hinge pin and knuckle barrel capped through a ribbed flex cuff, a long plated forearm, wrist cuff and collar, and a three-finger gripper — with deliberately rich five-bone weight bumps in the bellows, then enforces the game-engine maximum of four bone influences per vertex through the data API, following mesh-editing-and-bmesh and building on the linear-blend-skinning precedent of armature-bend.

Pipeline arc: modeling/LOD in lod-decimate-chain, weighting here, export in gltf-export-roundtrip.

What it witnesses: the skinning constraint every game engine enforces and AI-generated rigging code most often violates silently.

1. The limit is a data-API operation, not a context operator. Instead of bpy.ops.object.vertex_group_limit_total, the example reads each vertex's groups, keeps the top four by weight, VertexGroup.removes the rest, and renormalizes the survivors. Dropping without renormalizing leaves sums at 0.986 — a mesh that shrinks toward the origin under load (the check's measured failure, 1.438e-02 off unit sum). 2. The armature modifier is still exactly linear blend skinning after the limit: every depsgraph-evaluated vertex equals Σ wᵢ · (pose.matrix @ bone.matrix_local.inverted()) @ rest, with the weights read back from the mesh's own deform layer (v.groups) — the weights on the mesh are the contract, not the weights you meant to write. Measured lbs_err = 3.0e-07. 3. Pruning must not damage the pose. Evaluated positions before and after the limit are held within 0.05 (measured 3.0e-03), the pedestal mount stays exactly pinned (Root is unposed), and the pre-limit authoring really carries five influences in the boots — otherwise the witness would be vacuous.

The vertex-group API (v.groups, VertexGroup.add/remove) is stable between Blender 4.5 LTS and 5.1 — the example runs identically on both, which is itself the version witness (measured values match to the digit).

-

The render shows the pruned arm mid-pose: the flex bellows carry the teal accent — the five-influence zones the limit prunes glow at the elbow hinge and wrist, sealed by the bright ring on the elbow bellows — proof that the limited weights still deform as authored.

+

The render shows the pruned arm mid-pose: the flex cuffs carry the teal accent — the five-influence zones the limit prunes glow at the elbow hinge and wrist, sealed by the bright hoop on the elbow cuff — proof that the limited weights still deform as authored.

Run

# Cheap correctness check (no render) — the CI check:
 blender --background --python vertex_weight_limit.py --
@@ -337,23 +337,26 @@ 

Source

tag(lathe_part(bm, [(0.10, 0.14), (0.30, 0.19), (0.45, 0.19), (0.60, 0.23), (0.80, 0.24), (1.00, 0.23), (1.15, 0.21)], ORANGE), "Shoulder") - # clevis cheeks the hinge pin rides in (rigid on Shoulder) + # clevis cheek plates flanking the joint (rigid on Shoulder) for sy in (-1, 1): - box_part(bm, (0.12, 0.05, 0.42), (0.0, sy * 0.165, 1.24), + box_part(bm, (0.10, 0.06, 0.50), (0.0, sy * 0.175, 1.30), (0, 0, 0), ORANGE, part_of, "Shoulder") - # elbow flex bellows: the five-influence zone the limit prunes - tag(lathe_part(bm, [(1.15, 0.21), (1.20, 0.19), (1.26, 0.21), - (1.33, 0.19), (1.40, 0.21), (1.47, 0.19), - (1.55, 0.19)], RUBBER), "FLEX") - # the hinge pin through the cheeks, capped both ends (rides Elbow) - cone_part(bm, 0.15, 0.15, 0.46, (0.0, 0.0, 1.35), (90, 0, 0), + # elbow flex cuff behind the hinge: the five-influence zone the + # limit prunes (every ring inside the five-bump z window) + tag(lathe_part(bm, [(1.33, 0.17), (1.38, 0.19), (1.43, 0.17), + (1.49, 0.19), (1.55, 0.18)], RUBBER), "FLEX") + # the hinge pin stays with the clevis (Shoulder): the forearm's + # knuckle barrel (Elbow) rotates around it — pin caps must not tilt + cone_part(bm, 0.13, 0.13, 0.44, (0.0, 0.0, 1.35), (90, 0, 0), + GUNMETAL, part_of, "Shoulder") + cone_part(bm, 0.165, 0.165, 0.26, (0.0, 0.0, 1.35), (90, 0, 0), GUNMETAL, part_of, "Elbow") for sy in (-1, 1): - cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.215, 1.35), - (90, 0, 0), GUNMETAL, part_of, "Elbow") - # bright seal ring on the bellows middle — the accent marking the - # primary pruned-weight zone - tag(lathe_part(bm, [(1.33, 0.215), (1.37, 0.215)], ACCENT), "Elbow") + cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.225, 1.35), + (90, 0, 0), GUNMETAL, part_of, "Shoulder") + # bright seal hoop on the cuff — the accent marking the primary + # pruned-weight zone + tag(lathe_part(bm, [(1.42, 0.20), (1.46, 0.20)], ACCENT), "Elbow") # long plated forearm with two ridges (rigid on Elbow) tag(lathe_part(bm, [(1.55, 0.19), (1.75, 0.21), (1.80, 0.23), (1.86, 0.21), (2.05, 0.20), (2.18, 0.22), @@ -373,8 +376,8 @@

Source

# three two-segment fingers, splayed (rigid on Claw) for a_deg in (90.0, 210.0, 330.0): a = math.radians(a_deg) - for pos, tilt, size in (((0.08, 3.08), 12.0, (0.075, 0.11, 0.22)), - ((0.14, 3.26), 26.0, (0.06, 0.095, 0.18))): + for pos, tilt, size in (((0.08, 3.08), 12.0, (0.085, 0.13, 0.22)), + ((0.14, 3.26), 26.0, (0.07, 0.11, 0.18))): off, z = pos m = (mathutils.Matrix.Translation((0.0, 0.0, z)) @ mathutils.Matrix.Rotation(a, 4, 'Z') diff --git a/examples/vertex-weight-limit/README.md b/examples/vertex-weight-limit/README.md index 51d5463..c0e0856 100644 --- a/examples/vertex-weight-limit/README.md +++ b/examples/vertex-weight-limit/README.md @@ -1,9 +1,9 @@ # Vertex Weight Limit A runnable example that rigs a mech arm — bolted pedestal and shoulder -fairing, an upper arm ending in clevis cheeks, the elbow hinge pin capped -through a ribbed flex bellows, a long plated forearm, wrist bellows and -collar, and a three-finger gripper — with deliberately rich five-bone weight +fairing, an upper arm ending in clevis cheeks, the elbow hinge pin and +knuckle barrel capped through a ribbed flex cuff, a long plated forearm, +wrist cuff and collar, and a three-finger gripper — with deliberately rich five-bone weight bumps in the bellows, then enforces the game-engine **maximum of four bone influences per vertex** through the data API, following [`mesh-editing-and-bmesh`](../../skills/mesh-editing-and-bmesh/SKILL.md) and @@ -37,9 +37,9 @@ The vertex-group API (`v.groups`, `VertexGroup.add`/`remove`) is stable between Blender 4.5 LTS and 5.1 — the example runs identically on both, which is itself the version witness (measured values match to the digit). -The render shows the pruned arm mid-pose: the flex bellows carry the teal +The render shows the pruned arm mid-pose: the flex cuffs carry the teal accent — the five-influence zones the limit prunes glow at the elbow hinge -and wrist, sealed by the bright ring on the elbow bellows — proof that the +and wrist, sealed by the bright hoop on the elbow cuff — proof that the limited weights still deform as authored. ## Run diff --git a/examples/vertex-weight-limit/preview.webp b/examples/vertex-weight-limit/preview.webp index 69a5733..339f91b 100644 Binary files a/examples/vertex-weight-limit/preview.webp and b/examples/vertex-weight-limit/preview.webp differ diff --git a/examples/vertex-weight-limit/vertex_weight_limit.py b/examples/vertex-weight-limit/vertex_weight_limit.py index 12aa5bb..0403e4a 100644 --- a/examples/vertex-weight-limit/vertex_weight_limit.py +++ b/examples/vertex-weight-limit/vertex_weight_limit.py @@ -131,23 +131,26 @@ def tag(rings_verts, bone): tag(lathe_part(bm, [(0.10, 0.14), (0.30, 0.19), (0.45, 0.19), (0.60, 0.23), (0.80, 0.24), (1.00, 0.23), (1.15, 0.21)], ORANGE), "Shoulder") - # clevis cheeks the hinge pin rides in (rigid on Shoulder) + # clevis cheek plates flanking the joint (rigid on Shoulder) for sy in (-1, 1): - box_part(bm, (0.12, 0.05, 0.42), (0.0, sy * 0.165, 1.24), + box_part(bm, (0.10, 0.06, 0.50), (0.0, sy * 0.175, 1.30), (0, 0, 0), ORANGE, part_of, "Shoulder") - # elbow flex bellows: the five-influence zone the limit prunes - tag(lathe_part(bm, [(1.15, 0.21), (1.20, 0.19), (1.26, 0.21), - (1.33, 0.19), (1.40, 0.21), (1.47, 0.19), - (1.55, 0.19)], RUBBER), "FLEX") - # the hinge pin through the cheeks, capped both ends (rides Elbow) - cone_part(bm, 0.15, 0.15, 0.46, (0.0, 0.0, 1.35), (90, 0, 0), + # elbow flex cuff behind the hinge: the five-influence zone the + # limit prunes (every ring inside the five-bump z window) + tag(lathe_part(bm, [(1.33, 0.17), (1.38, 0.19), (1.43, 0.17), + (1.49, 0.19), (1.55, 0.18)], RUBBER), "FLEX") + # the hinge pin stays with the clevis (Shoulder): the forearm's + # knuckle barrel (Elbow) rotates around it — pin caps must not tilt + cone_part(bm, 0.13, 0.13, 0.44, (0.0, 0.0, 1.35), (90, 0, 0), + GUNMETAL, part_of, "Shoulder") + cone_part(bm, 0.165, 0.165, 0.26, (0.0, 0.0, 1.35), (90, 0, 0), GUNMETAL, part_of, "Elbow") for sy in (-1, 1): - cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.215, 1.35), - (90, 0, 0), GUNMETAL, part_of, "Elbow") - # bright seal ring on the bellows middle — the accent marking the - # primary pruned-weight zone - tag(lathe_part(bm, [(1.33, 0.215), (1.37, 0.215)], ACCENT), "Elbow") + cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.225, 1.35), + (90, 0, 0), GUNMETAL, part_of, "Shoulder") + # bright seal hoop on the cuff — the accent marking the primary + # pruned-weight zone + tag(lathe_part(bm, [(1.42, 0.20), (1.46, 0.20)], ACCENT), "Elbow") # long plated forearm with two ridges (rigid on Elbow) tag(lathe_part(bm, [(1.55, 0.19), (1.75, 0.21), (1.80, 0.23), (1.86, 0.21), (2.05, 0.20), (2.18, 0.22), @@ -167,8 +170,8 @@ def tag(rings_verts, bone): # three two-segment fingers, splayed (rigid on Claw) for a_deg in (90.0, 210.0, 330.0): a = math.radians(a_deg) - for pos, tilt, size in (((0.08, 3.08), 12.0, (0.075, 0.11, 0.22)), - ((0.14, 3.26), 26.0, (0.06, 0.095, 0.18))): + for pos, tilt, size in (((0.08, 3.08), 12.0, (0.085, 0.13, 0.22)), + ((0.14, 3.26), 26.0, (0.07, 0.11, 0.18))): off, z = pos m = (mathutils.Matrix.Translation((0.0, 0.0, z)) @ mathutils.Matrix.Rotation(a, 4, 'Z')