Skip to content

Commit 065bfce

Browse files
authored
fix: connect vertex-weight-limit's arm parts and straighten the reach (#81)
Round two on the hero: the shoulder hub caps read as floating wheels, the hinge caps floated off the clevis, the fingers hung above a palm void, the bellows recessed at both ends, and the curled claw jammed against the top of the frame. Removed the hub and rooted the upper arm deep in the fairing (articulation cannot open a gap), set the hinge caps flush on the clevis cheeks, grew the palm into the finger roots, matched bellows end radii to the shells they join, and relaxed Wrist/Claw so the claw holds a verified 103 px top margin (posed-vertex NDC projection, not eyeballing). Check contracts unchanged: pre_max=5, values identical on 5.1.2 and 4.5.11 (verts=1856, limited=128, sum 2.98e-08, pose 2.80e-03, lbs 2.98e-07). Re-falsified: exits 4/6/8 (sums 1.269e-02, LBS dev 1.777). Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
1 parent c706d0f commit 065bfce

6 files changed

Lines changed: 35 additions & 37 deletions

File tree

18 Bytes
Loading
-74 Bytes
Loading

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

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ <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, a shoulder hub and clevis-ended upper arm, the elbow hinge pin inside 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 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>
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>
@@ -248,7 +248,7 @@ <h2>Source</h2>
248248
BONE_SPANS = {<span class="s">&quot;Root&quot;</span>: (-<span class="n">0.35</span>, <span class="n">0.10</span>), <span class="s">&quot;Shoulder&quot;</span>: (<span class="n">0.10</span>, <span class="n">1.35</span>),
249249
<span class="s">&quot;Elbow&quot;</span>: (<span class="n">1.35</span>, <span class="n">2.55</span>), <span class="s">&quot;Wrist&quot;</span>: (<span class="n">2.55</span>, <span class="n">2.90</span>), <span class="s">&quot;Claw&quot;</span>: (<span class="n">2.90</span>, <span class="n">3.35</span>)}
250250
BONE_CENTERS = {b: (s[<span class="n">0</span>] + s[<span class="n">1</span>]) / <span class="n">2</span> <span class="k">for</span> b, s <span class="k">in</span> BONE_SPANS.items()}
251-
POSE_DEG = {<span class="s">&quot;Shoulder&quot;</span>: -<span class="n">8.0</span>, <span class="s">&quot;Elbow&quot;</span>: -<span class="n">35.0</span>, <span class="s">&quot;Wrist&quot;</span>: -<span class="n">18.0</span>, <span class="s">&quot;Claw&quot;</span>: <span class="n">12.0</span>}
251+
POSE_DEG = {<span class="s">&quot;Shoulder&quot;</span>: -<span class="n">8.0</span>, <span class="s">&quot;Elbow&quot;</span>: -<span class="n">35.0</span>, <span class="s">&quot;Wrist&quot;</span>: -<span class="n">14.0</span>, <span class="s">&quot;Claw&quot;</span>: <span class="n">6.0</span>}
252252

253253
<span class="c"># armor palette slots</span>
254254
GUNMETAL, ORANGE, RUBBER, ACCENT = <span class="n">0</span>, <span class="n">1</span>, <span class="n">2</span>, <span class="n">3</span>
@@ -332,25 +332,24 @@ <h2>Source</h2>
332332
cone_part(bm, <span class="n">0.05</span>, <span class="n">0.04</span>, <span class="n">0.06</span>,
333333
(<span class="n">0.36</span> * math.cos(a), <span class="n">0.36</span> * math.sin(a), <span class="n">0.03</span>),
334334
(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>), GUNMETAL, part_of, <span class="s">&quot;Root&quot;</span>)
335-
<span class="c"># shoulder hub poking through the fairing (rigid on Shoulder)</span>
336-
cone_part(bm, <span class="n">0.19</span>, <span class="n">0.19</span>, <span class="n">0.56</span>, (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.62</span>), (<span class="n">90</span>, <span class="n">0</span>, <span class="n">0</span>),
337-
GUNMETAL, part_of, <span class="s">&quot;Shoulder&quot;</span>)
338-
<span class="c"># upper arm shell, slimmer than the old barrel (rigid on Shoulder)</span>
339-
tag(lathe_part(bm, [(<span class="n">0.45</span>, <span class="n">0.19</span>), (<span class="n">0.60</span>, <span class="n">0.23</span>), (<span class="n">0.80</span>, <span class="n">0.24</span>),
340-
(<span class="n">1.00</span>, <span class="n">0.23</span>), (<span class="n">1.15</span>, <span class="n">0.21</span>)], ORANGE), <span class="s">&quot;Shoulder&quot;</span>)
335+
<span class="c"># upper arm shell rooted deep inside the fairing — no gap at the</span>
336+
<span class="c"># shoulder when the joint articulates (rigid on Shoulder)</span>
337+
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>),
338+
(<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>),
339+
(<span class="n">1.15</span>, <span class="n">0.21</span>)], ORANGE), <span class="s">&quot;Shoulder&quot;</span>)
341340
<span class="c"># clevis cheeks the hinge pin rides in (rigid on Shoulder)</span>
342341
<span class="k">for</span> sy <span class="k">in</span> (-<span class="n">1</span>, <span class="n">1</span>):
343342
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>),
344343
(<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>)
345344
<span class="c"># elbow flex bellows: the five-influence zone the limit prunes</span>
346-
tag(lathe_part(bm, [(<span class="n">1.15</span>, <span class="n">0.17</span>), (<span class="n">1.20</span>, <span class="n">0.20</span>), (<span class="n">1.26</span>, <span class="n">0.16</span>),
347-
(<span class="n">1.33</span>, <span class="n">0.20</span>), (<span class="n">1.40</span>, <span class="n">0.16</span>), (<span class="n">1.47</span>, <span class="n">0.20</span>),
348-
(<span class="n">1.55</span>, <span class="n">0.17</span>)], RUBBER), <span class="s">&quot;FLEX&quot;</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>)
349348
<span class="c"># the hinge pin through the cheeks, capped both ends (rides Elbow)</span>
350349
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>),
351350
GUNMETAL, part_of, <span class="s">&quot;Elbow&quot;</span>)
352351
<span class="k">for</span> sy <span class="k">in</span> (-<span class="n">1</span>, <span class="n">1</span>):
353-
cone_part(bm, <span class="n">0.18</span>, <span class="n">0.18</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>),
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>),
354353
(<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>)
355354
<span class="c"># bright seal ring on the bellows middle — the accent marking the</span>
356355
<span class="c"># primary pruned-weight zone</span>
@@ -363,19 +362,19 @@ <h2>Source</h2>
363362
box_part(bm, (<span class="n">0.10</span>, <span class="n">0.05</span>, <span class="n">0.60</span>), (<span class="n">0.0</span>, <span class="n">0.235</span>, <span class="n">2.02</span>),
364363
(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>), ORANGE, part_of, <span class="s">&quot;Elbow&quot;</span>)
365364
<span class="c"># wrist flex bellows (second blend zone) + collar</span>
366-
tag(lathe_part(bm, [(<span class="n">2.45</span>, <span class="n">0.16</span>), (<span class="n">2.51</span>, <span class="n">0.19</span>), (<span class="n">2.57</span>, <span class="n">0.15</span>),
367-
(<span class="n">2.64</span>, <span class="n">0.18</span>), (<span class="n">2.70</span>, <span class="n">0.15</span>), (<span class="n">2.75</span>, <span class="n">0.16</span>)],
365+
tag(lathe_part(bm, [(<span class="n">2.45</span>, <span class="n">0.18</span>), (<span class="n">2.51</span>, <span class="n">0.20</span>), (<span class="n">2.57</span>, <span class="n">0.16</span>),
366+
(<span class="n">2.64</span>, <span class="n">0.19</span>), (<span class="n">2.70</span>, <span class="n">0.16</span>), (<span class="n">2.75</span>, <span class="n">0.17</span>)],
368367
RUBBER), <span class="s">&quot;FLEX&quot;</span>)
369368
tag(lathe_part(bm, [(<span class="n">2.75</span>, <span class="n">0.16</span>), (<span class="n">2.85</span>, <span class="n">0.175</span>), (<span class="n">2.90</span>, <span class="n">0.16</span>)],
370369
GUNMETAL), <span class="s">&quot;Wrist&quot;</span>)
371370
<span class="c"># palm block (rigid on Wrist)</span>
372-
box_part(bm, (<span class="n">0.26</span>, <span class="n">0.20</span>, <span class="n">0.14</span>), (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">2.99</span>),
371+
box_part(bm, (<span class="n">0.30</span>, <span class="n">0.22</span>, <span class="n">0.24</span>), (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">2.98</span>),
373372
(<span class="n">0</span>, <span class="n">0</span>, <span class="n">0</span>), GUNMETAL, part_of, <span class="s">&quot;Wrist&quot;</span>)
374373
<span class="c"># three two-segment fingers, splayed (rigid on Claw)</span>
375374
<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>):
376375
a = math.radians(a_deg)
377-
<span class="k">for</span> pos, tilt, size <span class="k">in</span> (((<span class="n">0.09</span>, <span class="n">3.11</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>)),
378-
((<span class="n">0.15</span>, <span class="n">3.28</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>))):
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>))):
379378
off, z = pos
380379
m = (mathutils.Matrix.Translation((<span class="n">0.0</span>, <span class="n">0.0</span>, z))
381380
@ mathutils.Matrix.Rotation(a, <span class="n">4</span>, <span class="s">&#x27;Z&#x27;</span>)

examples/vertex-weight-limit/README.md

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

33
A runnable example that rigs a mech arm — bolted pedestal and shoulder
4-
fairing, a shoulder hub and clevis-ended upper arm, the elbow hinge pin inside
5-
a ribbed flex bellows, a long plated forearm, wrist bellows and collar, and a
6-
three-finger gripper — with deliberately rich five-bone weight bumps in the
7-
bellows, then enforces the game-engine
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
7+
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
1010
building on the linear-blend-skinning precedent of
42 Bytes
Loading

examples/vertex-weight-limit/vertex_weight_limit.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
BONE_SPANS = {"Root": (-0.35, 0.10), "Shoulder": (0.10, 1.35),
4343
"Elbow": (1.35, 2.55), "Wrist": (2.55, 2.90), "Claw": (2.90, 3.35)}
4444
BONE_CENTERS = {b: (s[0] + s[1]) / 2 for b, s in BONE_SPANS.items()}
45-
POSE_DEG = {"Shoulder": -8.0, "Elbow": -35.0, "Wrist": -18.0, "Claw": 12.0}
45+
POSE_DEG = {"Shoulder": -8.0, "Elbow": -35.0, "Wrist": -14.0, "Claw": 6.0}
4646

4747
# armor palette slots
4848
GUNMETAL, ORANGE, RUBBER, ACCENT = 0, 1, 2, 3
@@ -126,25 +126,24 @@ def tag(rings_verts, bone):
126126
cone_part(bm, 0.05, 0.04, 0.06,
127127
(0.36 * math.cos(a), 0.36 * math.sin(a), 0.03),
128128
(0, 0, 0), GUNMETAL, part_of, "Root")
129-
# shoulder hub poking through the fairing (rigid on Shoulder)
130-
cone_part(bm, 0.19, 0.19, 0.56, (0.0, 0.0, 0.62), (90, 0, 0),
131-
GUNMETAL, part_of, "Shoulder")
132-
# upper arm shell, slimmer than the old barrel (rigid on Shoulder)
133-
tag(lathe_part(bm, [(0.45, 0.19), (0.60, 0.23), (0.80, 0.24),
134-
(1.00, 0.23), (1.15, 0.21)], ORANGE), "Shoulder")
129+
# upper arm shell rooted deep inside the fairing — no gap at the
130+
# shoulder when the joint articulates (rigid on Shoulder)
131+
tag(lathe_part(bm, [(0.10, 0.14), (0.30, 0.19), (0.45, 0.19),
132+
(0.60, 0.23), (0.80, 0.24), (1.00, 0.23),
133+
(1.15, 0.21)], ORANGE), "Shoulder")
135134
# clevis cheeks the hinge pin rides in (rigid on Shoulder)
136135
for sy in (-1, 1):
137136
box_part(bm, (0.12, 0.05, 0.42), (0.0, sy * 0.165, 1.24),
138137
(0, 0, 0), ORANGE, part_of, "Shoulder")
139138
# elbow flex bellows: the five-influence zone the limit prunes
140-
tag(lathe_part(bm, [(1.15, 0.17), (1.20, 0.20), (1.26, 0.16),
141-
(1.33, 0.20), (1.40, 0.16), (1.47, 0.20),
142-
(1.55, 0.17)], RUBBER), "FLEX")
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")
143142
# the hinge pin through the cheeks, capped both ends (rides Elbow)
144143
cone_part(bm, 0.15, 0.15, 0.46, (0.0, 0.0, 1.35), (90, 0, 0),
145144
GUNMETAL, part_of, "Elbow")
146145
for sy in (-1, 1):
147-
cone_part(bm, 0.18, 0.18, 0.05, (0.0, sy * 0.225, 1.35),
146+
cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.215, 1.35),
148147
(90, 0, 0), GUNMETAL, part_of, "Elbow")
149148
# bright seal ring on the bellows middle — the accent marking the
150149
# primary pruned-weight zone
@@ -157,19 +156,19 @@ def tag(rings_verts, bone):
157156
box_part(bm, (0.10, 0.05, 0.60), (0.0, 0.235, 2.02),
158157
(0, 0, 0), ORANGE, part_of, "Elbow")
159158
# wrist flex bellows (second blend zone) + collar
160-
tag(lathe_part(bm, [(2.45, 0.16), (2.51, 0.19), (2.57, 0.15),
161-
(2.64, 0.18), (2.70, 0.15), (2.75, 0.16)],
159+
tag(lathe_part(bm, [(2.45, 0.18), (2.51, 0.20), (2.57, 0.16),
160+
(2.64, 0.19), (2.70, 0.16), (2.75, 0.17)],
162161
RUBBER), "FLEX")
163162
tag(lathe_part(bm, [(2.75, 0.16), (2.85, 0.175), (2.90, 0.16)],
164163
GUNMETAL), "Wrist")
165164
# palm block (rigid on Wrist)
166-
box_part(bm, (0.26, 0.20, 0.14), (0.0, 0.0, 2.99),
165+
box_part(bm, (0.30, 0.22, 0.24), (0.0, 0.0, 2.98),
167166
(0, 0, 0), GUNMETAL, part_of, "Wrist")
168167
# three two-segment fingers, splayed (rigid on Claw)
169168
for a_deg in (90.0, 210.0, 330.0):
170169
a = math.radians(a_deg)
171-
for pos, tilt, size in (((0.09, 3.11), 12.0, (0.075, 0.11, 0.22)),
172-
((0.15, 3.28), 26.0, (0.06, 0.095, 0.18))):
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))):
173172
off, z = pos
174173
m = (mathutils.Matrix.Translation((0.0, 0.0, z))
175174
@ mathutils.Matrix.Rotation(a, 4, 'Z')

0 commit comments

Comments
 (0)