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 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.
- 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.984 — a mesh that shrinks toward the origin under load (the check's measured failure, 1.616e-02 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. Measuredlbs_err = 2.7e-07. - 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 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 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.
# Cheap correctness check (no render) — the CI check:
blender --background --python vertex_weight_limit.py --
# Falsifier: skip the 4-influence prune. Must exit non-zero.
blender --background --python vertex_weight_limit.py -- --skip-limit
# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
blender --background --python vertex_weight_limit.py -- --output arm.png
blender --background --python vertex_weight_limit.py -- --output arm.png --engine cyclesPer-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 | Meaning |
|---|---|
| 0 | Success |
| 1 | Uncaught exception (FATAL wrapper) |
| 2 | argparse / usage |
| 3 | Pre-limit max influences ≠ 5 |
| 4 | Vertex over the 4-influence cap (--skip-limit lands here) |
| 5 | Limit changed nothing |
| 6 | Weight sums off 1.0 after renormalize |
| 7 | Pose damaged by pruning, or evaluated vert count changed |
| 8 | Evaluated mesh off LBS over limited weights |
| 9 | Root-weighted mount moved |
| 10 | Gallery framing violation; also --output produced no file |
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).
Smoke does not pass --output or --skip-limit.