Wave Displace
Bulk vertex IO at real scale — 9,409 vertices displaced into a standing wave with one foreach_get and one foreach_set, no per-vertex access.
examples/vertex-weight-limit/
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.
Rendered headless by the example itself. Select it to enlarge.
runs on 4.5 LTS · 5.1 · 5.2 LTS
blender --background --python examples/vertex-weight-limit/vertex_weight_limit.py --
falsify it Breaks the contract on purpose and must exit 4
blender --background --python examples/vertex-weight-limit/vertex_weight_limit.py -- --skip-limit
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 and building on the linear-blend-skinning precedent of 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, 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.
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 short of one — a mesh that shrinks toward the origin under load (the check's measured failure, 2.647e-03 off unit sum).Σ 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 = 1.2e-06.The vertex-group API (v.groups, VertexGroup.add/remove) is stable between 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).
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.
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.
# 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 cycles
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. 11 is the shared asset-quality helper.
| 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 (--output only; gallery_framing) |
| 11 | Asset-quality floor violation (--output only) |
| 12 | --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. Its catalog falsifier is --skip-limit (expects exit 4).