Skip to content

Commit d8037f5

Browse files
authored
fix: rebuild the elbow as a readable hinge in vertex-weight-limit (#82)
The elbow was a fat teal donut swallowing the whole joint, and the hinge cap tilted with the forearm because pin and caps were skinned to Elbow. Rebuilt as an actual hinge: visible clevis cheek plates, the pin and its flush bolt caps skinned to Shoulder (they no longer tilt), the forearm's knuckle barrel rotating around the pin, and the five-influence flex zone slimmed to a ribbed cuff behind the hinge with the bright seal hoop on it. Fingers chunked up so the gripper reads at distance. pre_max=5 holds (every cuff ring sits inside the five-bump z window); measured values identical on 5.1.2 and 4.5.11 (verts=1840, limited=160, sum 2.98e-08, pose 2.76e-03, lbs 2.98e-07). Re-falsified: exits 4/6/8 (sums 1.616e-02, LBS dev 1.779). Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
1 parent 69ba57d commit d8037f5

6 files changed

Lines changed: 43 additions & 37 deletions

File tree

-470 Bytes
Loading
-66 Bytes
Loading

docs/gallery/vertex-weight-limit/index.html

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@ <h1>vertex-weight-limit</h1>
183183
<button class="copy-btn" id="copyRun" type="button">Copy</button>
184184
</div>
185185
<section class="detail-section md">
186-
<p>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 <strong>maximum of four bone influences per vertex</strong> through the data API, following <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/mesh-editing-and-bmesh/SKILL.md"><code>mesh-editing-and-bmesh</code></a> and building on the linear-blend-skinning precedent of <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/armature-bend"><code>armature-bend</code></a>.</p>
186+
<p>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 <strong>maximum of four bone influences per vertex</strong> through the data API, following <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/mesh-editing-and-bmesh/SKILL.md"><code>mesh-editing-and-bmesh</code></a> and building on the linear-blend-skinning precedent of <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/armature-bend"><code>armature-bend</code></a>.</p>
187187
<p><strong>Pipeline arc:</strong> modeling/LOD in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/lod-decimate-chain"><code>lod-decimate-chain</code></a>, weighting here, export in <a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gltf-export-roundtrip"><code>gltf-export-roundtrip</code></a>.</p>
188188
<p><strong>What it witnesses:</strong> the skinning constraint every game engine enforces and AI-generated rigging code most often violates silently.</p>
189189
<p>1. <strong>The limit is a data-API operation, not a context operator.</strong> Instead of <code>bpy.ops.object.vertex_group_limit_total</code>, the example reads each vertex&#x27;s groups, keeps the top four by weight, <code>VertexGroup.remove</code>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&#x27;s measured failure, 1.438e-02 off unit sum). 2. <strong>The armature modifier is still exactly linear blend skinning</strong> after the limit: every depsgraph-evaluated vertex equals <code>Σ wᵢ · (pose.matrix @ bone.matrix_local.inverted()) @ rest</code>, with the weights <strong>read back from the mesh&#x27;s own deform layer</strong> (<code>v.groups</code>) — the weights on the mesh are the contract, not the weights you meant to write. Measured <code>lbs_err = 3.0e-07</code>. 3. <strong>Pruning must not damage the pose.</strong> 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.</p>
190190
<p>The vertex-group API (<code>v.groups</code>, <code>VertexGroup.add</code>/<code>remove</code>) 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).</p>
191-
<p>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.</p>
191+
<p>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.</p>
192192
<h2>Run</h2>
193193
<pre><code># Cheap correctness check (no render) — the CI check:
194194
blender --background --python vertex_weight_limit.py --
@@ -337,23 +337,26 @@ <h2>Source</h2>
337337
tag(lathe_part(bm, [(<span class="n">0.10</span>, <span class="n">0.14</span>), (<span class="n">0.30</span>, <span class="n">0.19</span>), (<span class="n">0.45</span>, <span class="n">0.19</span>),
338338
(<span class="n">0.60</span>, <span class="n">0.23</span>), (<span class="n">0.80</span>, <span class="n">0.24</span>), (<span class="n">1.00</span>, <span class="n">0.23</span>),
339339
(<span class="n">1.15</span>, <span class="n">0.21</span>)], ORANGE), <span class="s">&quot;Shoulder&quot;</span>)
340-
<span class="c"># clevis cheeks the hinge pin rides in (rigid on Shoulder)</span>
340+
<span class="c"># clevis cheek plates flanking the joint (rigid on Shoulder)</span>
341341
<span class="k">for</span> sy <span class="k">in</span> (-<span class="n">1</span>, <span class="n">1</span>):
342-
box_part(bm, (<span class="n">0.12</span>, <span class="n">0.05</span>, <span class="n">0.42</span>), (<span class="n">0.0</span>, sy * <span class="n">0.165</span>, <span class="n">1.24</span>),
342+
box_part(bm, (<span class="n">0.10</span>, <span class="n">0.06</span>, <span class="n">0.50</span>), (<span class="n">0.0</span>, sy * <span class="n">0.175</span>, <span class="n">1.30</span>),
343343
(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>), ORANGE, part_of, <span class="s">&quot;Shoulder&quot;</span>)
344-
<span class="c"># elbow flex bellows: the five-influence zone the limit prunes</span>
345-
tag(lathe_part(bm, [(<span class="n">1.15</span>, <span class="n">0.21</span>), (<span class="n">1.20</span>, <span class="n">0.19</span>), (<span class="n">1.26</span>, <span class="n">0.21</span>),
346-
(<span class="n">1.33</span>, <span class="n">0.19</span>), (<span class="n">1.40</span>, <span class="n">0.21</span>), (<span class="n">1.47</span>, <span class="n">0.19</span>),
347-
(<span class="n">1.55</span>, <span class="n">0.19</span>)], RUBBER), <span class="s">&quot;FLEX&quot;</span>)
348-
<span class="c"># the hinge pin through the cheeks, capped both ends (rides Elbow)</span>
349-
cone_part(bm, <span class="n">0.15</span>, <span class="n">0.15</span>, <span class="n">0.46</span>, (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">1.35</span>), (<span class="n">90</span>, <span class="n">0</span>, <span class="n">0</span>),
344+
<span class="c"># elbow flex cuff behind the hinge: the five-influence zone the</span>
345+
<span class="c"># limit prunes (every ring inside the five-bump z window)</span>
346+
tag(lathe_part(bm, [(<span class="n">1.33</span>, <span class="n">0.17</span>), (<span class="n">1.38</span>, <span class="n">0.19</span>), (<span class="n">1.43</span>, <span class="n">0.17</span>),
347+
(<span class="n">1.49</span>, <span class="n">0.19</span>), (<span class="n">1.55</span>, <span class="n">0.18</span>)], RUBBER), <span class="s">&quot;FLEX&quot;</span>)
348+
<span class="c"># the hinge pin stays with the clevis (Shoulder): the forearm&#x27;s</span>
349+
<span class="c"># knuckle barrel (Elbow) rotates around it — pin caps must not tilt</span>
350+
cone_part(bm, <span class="n">0.13</span>, <span class="n">0.13</span>, <span class="n">0.44</span>, (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">1.35</span>), (<span class="n">90</span>, <span class="n">0</span>, <span class="n">0</span>),
351+
GUNMETAL, part_of, <span class="s">&quot;Shoulder&quot;</span>)
352+
cone_part(bm, <span class="n">0.165</span>, <span class="n">0.165</span>, <span class="n">0.26</span>, (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">1.35</span>), (<span class="n">90</span>, <span class="n">0</span>, <span class="n">0</span>),
350353
GUNMETAL, part_of, <span class="s">&quot;Elbow&quot;</span>)
351354
<span class="k">for</span> sy <span class="k">in</span> (-<span class="n">1</span>, <span class="n">1</span>):
352-
cone_part(bm, <span class="n">0.17</span>, <span class="n">0.17</span>, <span class="n">0.05</span>, (<span class="n">0.0</span>, sy * <span class="n">0.215</span>, <span class="n">1.35</span>),
353-
(<span class="n">90</span>, <span class="n">0</span>, <span class="n">0</span>), GUNMETAL, part_of, <span class="s">&quot;Elbow&quot;</span>)
354-
<span class="c"># bright seal ring on the bellows middle — the accent marking the</span>
355-
<span class="c"># primary pruned-weight zone</span>
356-
tag(lathe_part(bm, [(<span class="n">1.33</span>, <span class="n">0.215</span>), (<span class="n">1.37</span>, <span class="n">0.215</span>)], ACCENT), <span class="s">&quot;Elbow&quot;</span>)
355+
cone_part(bm, <span class="n">0.17</span>, <span class="n">0.17</span>, <span class="n">0.05</span>, (<span class="n">0.0</span>, sy * <span class="n">0.225</span>, <span class="n">1.35</span>),
356+
(<span class="n">90</span>, <span class="n">0</span>, <span class="n">0</span>), GUNMETAL, part_of, <span class="s">&quot;Shoulder&quot;</span>)
357+
<span class="c"># bright seal hoop on the cuff — the accent marking the primary</span>
358+
<span class="c"># pruned-weight zone</span>
359+
tag(lathe_part(bm, [(<span class="n">1.42</span>, <span class="n">0.20</span>), (<span class="n">1.46</span>, <span class="n">0.20</span>)], ACCENT), <span class="s">&quot;Elbow&quot;</span>)
357360
<span class="c"># long plated forearm with two ridges (rigid on Elbow)</span>
358361
tag(lathe_part(bm, [(<span class="n">1.55</span>, <span class="n">0.19</span>), (<span class="n">1.75</span>, <span class="n">0.21</span>), (<span class="n">1.80</span>, <span class="n">0.23</span>),
359362
(<span class="n">1.86</span>, <span class="n">0.21</span>), (<span class="n">2.05</span>, <span class="n">0.20</span>), (<span class="n">2.18</span>, <span class="n">0.22</span>),
@@ -373,8 +376,8 @@ <h2>Source</h2>
373376
<span class="c"># three two-segment fingers, splayed (rigid on Claw)</span>
374377
<span class="k">for</span> a_deg <span class="k">in</span> (<span class="n">90.0</span>, <span class="n">210.0</span>, <span class="n">330.0</span>):
375378
a = math.radians(a_deg)
376-
<span class="k">for</span> pos, tilt, size <span class="k">in</span> (((<span class="n">0.08</span>, <span class="n">3.08</span>), <span class="n">12.0</span>, (<span class="n">0.075</span>, <span class="n">0.11</span>, <span class="n">0.22</span>)),
377-
((<span class="n">0.14</span>, <span class="n">3.26</span>), <span class="n">26.0</span>, (<span class="n">0.06</span>, <span class="n">0.095</span>, <span class="n">0.18</span>))):
379+
<span class="k">for</span> pos, tilt, size <span class="k">in</span> (((<span class="n">0.08</span>, <span class="n">3.08</span>), <span class="n">12.0</span>, (<span class="n">0.085</span>, <span class="n">0.13</span>, <span class="n">0.22</span>)),
380+
((<span class="n">0.14</span>, <span class="n">3.26</span>), <span class="n">26.0</span>, (<span class="n">0.07</span>, <span class="n">0.11</span>, <span class="n">0.18</span>))):
378381
off, z = pos
379382
m = (mathutils.Matrix.Translation((<span class="n">0.0</span>, <span class="n">0.0</span>, z))
380383
@ mathutils.Matrix.Rotation(a, <span class="n">4</span>, <span class="s">&#x27;Z&#x27;</span>)

examples/vertex-weight-limit/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Vertex Weight Limit
22

33
A runnable example that rigs a mech arm — bolted pedestal and shoulder
4-
fairing, an upper arm ending in clevis cheeks, the elbow hinge pin capped
5-
through a ribbed flex bellows, a long plated forearm, wrist bellows and
6-
collar, and a three-finger gripper — with deliberately rich five-bone weight
4+
fairing, an upper arm ending in clevis cheeks, the elbow hinge pin and
5+
knuckle barrel capped through a ribbed flex cuff, a long plated forearm,
6+
wrist cuff and collar, and a three-finger gripper — with deliberately rich five-bone weight
77
bumps in the bellows, then enforces the game-engine
88
**maximum of four bone influences per vertex** through the data API, following
99
[`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
3737
Blender 4.5 LTS and 5.1 — the example runs identically on both, which is itself
3838
the version witness (measured values match to the digit).
3939

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

4545
## Run
20 Bytes
Loading

examples/vertex-weight-limit/vertex_weight_limit.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,23 +131,26 @@ def tag(rings_verts, bone):
131131
tag(lathe_part(bm, [(0.10, 0.14), (0.30, 0.19), (0.45, 0.19),
132132
(0.60, 0.23), (0.80, 0.24), (1.00, 0.23),
133133
(1.15, 0.21)], ORANGE), "Shoulder")
134-
# clevis cheeks the hinge pin rides in (rigid on Shoulder)
134+
# clevis cheek plates flanking the joint (rigid on Shoulder)
135135
for sy in (-1, 1):
136-
box_part(bm, (0.12, 0.05, 0.42), (0.0, sy * 0.165, 1.24),
136+
box_part(bm, (0.10, 0.06, 0.50), (0.0, sy * 0.175, 1.30),
137137
(0, 0, 0), ORANGE, part_of, "Shoulder")
138-
# elbow flex bellows: the five-influence zone the limit prunes
139-
tag(lathe_part(bm, [(1.15, 0.21), (1.20, 0.19), (1.26, 0.21),
140-
(1.33, 0.19), (1.40, 0.21), (1.47, 0.19),
141-
(1.55, 0.19)], RUBBER), "FLEX")
142-
# the hinge pin through the cheeks, capped both ends (rides Elbow)
143-
cone_part(bm, 0.15, 0.15, 0.46, (0.0, 0.0, 1.35), (90, 0, 0),
138+
# elbow flex cuff behind the hinge: the five-influence zone the
139+
# limit prunes (every ring inside the five-bump z window)
140+
tag(lathe_part(bm, [(1.33, 0.17), (1.38, 0.19), (1.43, 0.17),
141+
(1.49, 0.19), (1.55, 0.18)], RUBBER), "FLEX")
142+
# the hinge pin stays with the clevis (Shoulder): the forearm's
143+
# knuckle barrel (Elbow) rotates around it — pin caps must not tilt
144+
cone_part(bm, 0.13, 0.13, 0.44, (0.0, 0.0, 1.35), (90, 0, 0),
145+
GUNMETAL, part_of, "Shoulder")
146+
cone_part(bm, 0.165, 0.165, 0.26, (0.0, 0.0, 1.35), (90, 0, 0),
144147
GUNMETAL, part_of, "Elbow")
145148
for sy in (-1, 1):
146-
cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.215, 1.35),
147-
(90, 0, 0), GUNMETAL, part_of, "Elbow")
148-
# bright seal ring on the bellows middle — the accent marking the
149-
# primary pruned-weight zone
150-
tag(lathe_part(bm, [(1.33, 0.215), (1.37, 0.215)], ACCENT), "Elbow")
149+
cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.225, 1.35),
150+
(90, 0, 0), GUNMETAL, part_of, "Shoulder")
151+
# bright seal hoop on the cuff — the accent marking the primary
152+
# pruned-weight zone
153+
tag(lathe_part(bm, [(1.42, 0.20), (1.46, 0.20)], ACCENT), "Elbow")
151154
# long plated forearm with two ridges (rigid on Elbow)
152155
tag(lathe_part(bm, [(1.55, 0.19), (1.75, 0.21), (1.80, 0.23),
153156
(1.86, 0.21), (2.05, 0.20), (2.18, 0.22),
@@ -167,8 +170,8 @@ def tag(rings_verts, bone):
167170
# three two-segment fingers, splayed (rigid on Claw)
168171
for a_deg in (90.0, 210.0, 330.0):
169172
a = math.radians(a_deg)
170-
for pos, tilt, size in (((0.08, 3.08), 12.0, (0.075, 0.11, 0.22)),
171-
((0.14, 3.26), 26.0, (0.06, 0.095, 0.18))):
173+
for pos, tilt, size in (((0.08, 3.08), 12.0, (0.085, 0.13, 0.22)),
174+
((0.14, 3.26), 26.0, (0.07, 0.11, 0.18))):
172175
off, z = pos
173176
m = (mathutils.Matrix.Translation((0.0, 0.0, z))
174177
@ mathutils.Matrix.Rotation(a, 4, 'Z')

0 commit comments

Comments
 (0)