diff --git a/docs/gallery/vertex-weight-limit/index.html b/docs/gallery/vertex-weight-limit/index.html
index fd18821..0a47222 100644
--- a/docs/gallery/vertex-weight-limit/index.html
+++ b/docs/gallery/vertex-weight-limit/index.html
@@ -186,7 +186,7 @@
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 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.
+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.984 — a mesh that shrinks toward the origin under load (the check's measured failure, 1.616e-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 = 2.7e-07. 3. 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.
Run
@@ -240,7 +240,7 @@ Source
SIDES = 32
MAX_INFLUENCES = 4 # the engine constraint being witnessed
-BUMP_R = 1.8 # flex-boot blend support; guarantees 5 pre-limit
+BUMP_R = 1.8 # flex-cuff blend support; guarantees 5 pre-limit
LBS_TOL = 5e-4 # float32 mesh coords vs double pose matrices
SUM_TOL = 1e-5 # per-vertex weight sum after renormalize
POSE_TOL = 0.05 # pruning must not move the pose beyond this
@@ -261,7 +261,7 @@ Source
def flex_weights(z):
- """Rich pre-limit weights over all five bones — boots blend broadly."""
+ """Rich pre-limit weights over all five bones — cuffs blend broadly."""
w = [bump(z, BONE_CENTERS[b]) for b in BONES]
total = sum(w)
return [x / total for x in w]
@@ -408,7 +408,7 @@ Source
def assign_weights(obj, part_of):
"""Author the rich pre-limit weights: hard single-bone on armor, broad
- five-bone bumps in the flex boots."""
+ five-bone bumps in the flex cuffs."""
groups = {b: obj.vertex_groups.new(name=b) for b in BONES}
if len(part_of) != len(obj.data.vertices):
raise RuntimeError(f"part tag count {len(part_of)} != vert count "
@@ -466,7 +466,7 @@ Source
def check(obj, arm, groups, pose_before):
me = obj.data
- # pre-limit witness: the flex boots really carry five influences
+ # pre-limit witness: the flex cuffs really carry five influences
pre_max = max(len(v.groups) for v in me.vertices)
if pre_max != 5:
print(f"ERROR: pre-limit max influences {pre_max} != 5 — the rich "
diff --git a/examples/vertex-weight-limit/README.md b/examples/vertex-weight-limit/README.md
index 4eea9d4..0e5d23a 100644
--- a/examples/vertex-weight-limit/README.md
+++ b/examples/vertex-weight-limit/README.md
@@ -21,18 +21,18 @@ 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.remove`s 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).
+ 0.984 — a mesh that shrinks toward the origin under load (the check's
+ measured failure, 1.616e-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`.
+ Measured `lbs_err = 2.7e-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
+ 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 boots — otherwise the witness would be vacuous.
+ 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
diff --git a/examples/vertex-weight-limit/vertex_weight_limit.py b/examples/vertex-weight-limit/vertex_weight_limit.py
index c544898..a1ad562 100644
--- a/examples/vertex-weight-limit/vertex_weight_limit.py
+++ b/examples/vertex-weight-limit/vertex_weight_limit.py
@@ -34,7 +34,7 @@
SIDES = 32
MAX_INFLUENCES = 4 # the engine constraint being witnessed
-BUMP_R = 1.8 # flex-boot blend support; guarantees 5 pre-limit
+BUMP_R = 1.8 # flex-cuff blend support; guarantees 5 pre-limit
LBS_TOL = 5e-4 # float32 mesh coords vs double pose matrices
SUM_TOL = 1e-5 # per-vertex weight sum after renormalize
POSE_TOL = 0.05 # pruning must not move the pose beyond this
@@ -55,7 +55,7 @@ def bump(z, center, radius=BUMP_R):
def flex_weights(z):
- """Rich pre-limit weights over all five bones — boots blend broadly."""
+ """Rich pre-limit weights over all five bones — cuffs blend broadly."""
w = [bump(z, BONE_CENTERS[b]) for b in BONES]
total = sum(w)
return [x / total for x in w]
@@ -202,7 +202,7 @@ def tag(rings_verts, bone):
def assign_weights(obj, part_of):
"""Author the rich pre-limit weights: hard single-bone on armor, broad
- five-bone bumps in the flex boots."""
+ five-bone bumps in the flex cuffs."""
groups = {b: obj.vertex_groups.new(name=b) for b in BONES}
if len(part_of) != len(obj.data.vertices):
raise RuntimeError(f"part tag count {len(part_of)} != vert count "
@@ -260,7 +260,7 @@ def eval_positions(obj):
def check(obj, arm, groups, pose_before):
me = obj.data
- # pre-limit witness: the flex boots really carry five influences
+ # pre-limit witness: the flex cuffs really carry five influences
pre_max = max(len(v.groups) for v in me.vertices)
if pre_max != 5:
print(f"ERROR: pre-limit max influences {pre_max} != 5 — the rich "