Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ Neighbor of [`lod-decimate-chain`](examples/lod-decimate-chain/).
</tr>
<tr>
<td width="46%" valign="middle">
<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, a glowing teal flex cuff around the hex-bolted elbow hinge, long plated forearm, three-finger gripper - deformed by weights pruned to the four-influence engine cap" /></a>
<a href="examples/vertex-weight-limit/"><img src="examples/vertex-weight-limit/preview.webp" alt="Vertex weight limit: an orange industrial robot arm reaching down with a two-jaw gripper, painted with its own post-limit skin weights - each rigid segment glows in its bone colour and the back cables grade blue to teal to violet to magenta across the joints" /></a>
</td>
<td valign="middle">

Expand All @@ -908,9 +908,10 @@ Neighbor of [`lod-decimate-chain`](examples/lod-decimate-chain/).
The game-engine max-four-bone-influences constraint, enforced through the data
API (`v.groups` + `VertexGroup.remove` + renormalize) rather than the
`bpy.ops.object.vertex_group_limit_total` context path. Asserts the pre-limit
boots really carry five influences, no vertex ends over the cap, weights still
cables 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.
blend skinning read back from the mesh's own deform layer. The render paints
those post-limit weights onto the arm as a colour attribute.

</td>
</tr>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/gallery/assets/vertex-weight-limit-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -640,11 +640,11 @@ <h2><a href="lod-decimate-chain/">lod-decimate-chain</a></h2>
</article>
<article class="card" data-tags="armature mesh">
<a class="card-media" href="vertex-weight-limit/" tabindex="-1" aria-hidden="true">
<img src="assets/vertex-weight-limit-hero.webp" alt="A segmented orange robotic arm on a round base, bent at the elbow, with teal bands at the joints." loading="lazy" decoding="async" />
<img src="assets/vertex-weight-limit-hero.webp" alt="An orange industrial robot arm reaching down with a two-jaw gripper, each segment glowing in its bone colour and the cables on its back grading from colour to colour across each joint." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="vertex-weight-limit/">vertex-weight-limit</a></h2>
<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>
<p class="teaches">A rigged industrial robot 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>
<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>
<a class="card-link" href="vertex-weight-limit/">View example<span class="sr-only"> vertex-weight-limit</span> <span aria-hidden="true">&rarr;</span></a>
</div>
Expand Down
677 changes: 468 additions & 209 deletions docs/gallery/vertex-weight-limit/index.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/gallery.json
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,8 @@
{
"name": "vertex-weight-limit",
"dir": "examples/vertex-weight-limit",
"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.",
"alt": "A segmented orange robotic arm on a round base, bent at the elbow, with teal bands at the joints.",
"teaches": "A rigged industrial robot 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.",
"alt": "An orange industrial robot arm reaching down with a two-jaw gripper, each segment glowing in its bone colour and the cables on its back grading from colour to colour across each joint.",
"witnessesFix": "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's own deform layer, not the authoring function.",
"hero": "docs/gallery/assets/vertex-weight-limit-hero.webp",
"preview": "examples/vertex-weight-limit/preview.webp",
Expand Down
67 changes: 44 additions & 23 deletions examples/vertex-weight-limit/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Vertex Weight Limit

A runnable example that rigs a mech arm — flanged bolted pedestal and
shoulder fairing, a panel-seamed upper arm ending in clevis cheeks, the
elbow hinge pin and knuckle barrel capped with hex bolts through a ribbed
flex cuff, a long seam-grooved forearm, wrist cuff and collar, and a
three-finger gripper — with deliberately rich five-bone weight bumps in
the cuffs, 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
building on the linear-blend-skinning precedent of
A runnable example that rigs a six-axis-style industrial robot arm — bolted
floor plinth and turret drum with a finned rear drive pack, clevis cheeks
and servo drums at the shoulder and elbow, a tapered box-section upper arm
and forearm, a gas-spring balancer across the shoulder, a wrist fork and a
two-jaw gripper — as **one skinned mesh**, 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 building on the linear-blend-skinning precedent of
[`armature-bend`](../armature-bend/).

Every rigid part (armor, motors, cheeks, piston, gripper) is weighted 1.0 to
one bone. The two cable runs along the arm's back are the flex parts: they
hand over smoothly from bone to bone across each joint and carry the tail an
auto-weighting pass leaves behind — a small, distance-ranked spill onto all
five bones. That tail is the five-influence authoring the limit prunes.

**Pipeline arc:** modeling/LOD in [`lod-decimate-chain`](../lod-decimate-chain/),
weighting here, export in [`gltf-export-roundtrip`](../gltf-export-roundtrip/).

Expand All @@ -20,28 +25,43 @@ AI-generated rigging code most often violates silently.
- **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.remove`s the rest, and
renormalizes the survivors. Dropping without renormalizing leaves sums at
0.984 — a mesh that shrinks toward the origin under load (the check's
measured failure, 1.616e-02 off unit sum).
renormalizes the survivors. Dropping without renormalizing leaves sums
short of one — a mesh that shrinks toward the origin under load (the
check's measured failure, 2.647e-03 off unit sum).
- **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 = 2.7e-07`.
Measured `lbs_err = 1.2e-06`.
- **Pruning must not damage the pose.** Evaluated positions before and after
the limit are held within 0.05 (measured 2.8e-03), the pedestal mount stays
exactly pinned (Root is unposed), and the pre-limit authoring really carries
five influences in the cuffs — otherwise the witness would be vacuous.
the limit are held within 0.05 (measured 4.2e-03 over 3108 limited
vertices), the plinth stays exactly pinned (Root is unposed), and the
pre-limit authoring really carries five influences on the cables —
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).
Blender 4.5 LTS and 5.2 — the example runs identically on 4.5, 5.1 and 5.2,
which is itself the version witness (measured values match to the digit).

## What the render shows

The render path paints the **post-limit weights** onto the arm as a
`BoneBlend` colour attribute — `Σ wᵢ · colourᵢ` per vertex, read back from
`v.groups` after the prune — and the cables, side hatches and trim bands
display it through a matte Attribute-node material. Each rigid segment
therefore shows its bone's flat colour (blue turret, teal upper arm, violet
forearm, magenta wrist, lime gripper), and the cables grade from colour to
colour exactly where the skin weights blend across each joint, while the
posed arm shows those weights deforming it.

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.
**Numeric-only contract** (docs/VISUAL-STYLE.md § The render is the proof):
the four-influence cap itself has no visual signature. The pruned fifth
weight is the small spill (at most 0.0027 on any vertex), so a five-influence
arm deforms and paints indistinguishably — the prune preserves the pose by
design, and that is what check 3 asserts. The render depicts the subject and
where its weights blend; the cap is witnessed by the numbers above and by the
`--skip-limit` falsifier.

## Run

Expand All @@ -61,7 +81,7 @@ blender --background --python vertex_weight_limit.py -- --output arm.png --engin

Per-script sequential checks. `9` is a valid check code; there is no rule
against it. `10` is the shared framing helper; it is also the missing-render
code.
code. `11` is the shared asset-quality helper.

| Code | Meaning |
| --- | --- |
Expand All @@ -76,6 +96,7 @@ code.
| 8 | Evaluated mesh off LBS over limited weights |
| 9 | Root-weighted mount moved |
| 10 | Gallery framing violation; also `--output` produced no file |
| 11 | Asset-quality floor violation (`--output` only) |

The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
Expand Down
Binary file modified examples/vertex-weight-limit/preview.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading