Skip to content

Commit e173e9d

Browse files
TMHSDigitalclaude
andauthored
fix(examples): redesign gltf-skin-roundtrip as a mech scorpion standoff (#265)
* fix(examples): redesign gltf-skin-roundtrip as a mech scorpion standoff The review singled this hero out: two near-identical scorpions built from untextured orange boxes, with legs clipping into each other and the floor. The image gave the eye nothing to compare, because a pass meant "they look the same". The model is rebuilt from scratch. It has a lofted gunmetal chassis under a crowned carapace and five shingled tergite plates. Eight jointed legs have painted femur armour, hydraulic rams and knee drums, and they plant on rubber pads. Each claw has three bones (arm, swollen chela, movable finger). The tail has five armoured collars on rubber bellows, and a telson with a venom band and a chrome needle. There are 5 materials, and emission is used only on the eyes and the venom band. The rig grows from 7 bones to 21, with bones pointing +Y, -Y and +/-X, so the rest-matrix round-trip is exercised off the old single axis. The composition now witnesses the skin. The authored rig holds the guard pose the check compares. The actual re-imported mesh is driven through its IMPORTED armature into a different strike pose. A file that lost the skin, the joints or the weights could not follow that pose. The render path now runs gallery_framing (exit 10) and gallery_asset_quality (exit 11). Two check-protocol bugs were found and fixed; both make the check stricter: - The "straddle-safe" neighbour lookup added +/-1e-4 without re-rounding, so only the exact key could ever match. It reported a phantom weight loss (exit 18, 1.0) once the redesign put a vertex on a rounding boundary. - got_w kept only the last re-imported vertex per position. A corrupted split copy hid behind its twin, and the weight falsifier fell through to exit 19 instead of 18. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(examples): regate gltf-skin-roundtrip against the rebased calibration sets After the rebase onto main, two things changed. #258 re-rendered bmesh-gear, which darkened the contact-sheet calibration, and #257 raised the asset-sheet reference set. The scorpion stage measured 0.236, brighter than every calibration tile, so the key, fill, rim and wedge come down until the stage sits inside the set (0.204). Luma is now 0.231. The committed asset-sheet tool gains a row for this example, and it now bakes an Armature-deformed asset's staged pose before it removes the rig. Without that bake a skinned asset falls back to its rest pose, and the scorpion rendered with its tail stretched flat. None of the five references is skinned, so their panels are unchanged. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 4c82123 commit e173e9d

12 files changed

Lines changed: 1257 additions & 511 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -934,13 +934,13 @@ hazard that silently corrupts measurements on 4.5.
934934
</tr>
935935
<tr>
936936
<td width="46%" valign="middle">
937-
<a href="examples/gltf-skin-roundtrip/"><img src="examples/gltf-skin-roundtrip/preview.webp" alt="glTF skin round-trip: two identical mech scorpions on a dark studio floor - orange armor, black segmented tails curled over their backs with glowing stinger tips, teal eyes - proving skeleton, weights, and deformation survive the export" /></a>
937+
<a href="examples/gltf-skin-roundtrip/"><img src="examples/gltf-skin-roundtrip/preview.webp" alt="glTF skin round-trip: two orange-plated mech scorpions face off on a dark studio floor - the authored one on the left with its tail coiled over its back and claws tucked, the re-imported one on the right driven through its imported bones into a strike, tail reared high and claws raised" /></a>
938938
</td>
939939
<td valign="middle">
940940

941941
### [gltf-skin-roundtrip](examples/gltf-skin-roundtrip/)
942942

943-
The skinning counterpart to `gltf-export-roundtrip`: a seven-bone rigged
943+
The skinning counterpart to `gltf-export-roundtrip`: a 21-bone rigged mech
944944
scorpion exported with `export_skins` and re-imported, asserting the joint
945945
list, JOINTS_0/WEIGHTS_0 unit sums, bone/parent/rest-matrix round-trip,
946946
bit-exact weights, and identical deformation of the re-imported rig — plus
54.8 KB
Loading
14.4 KB
Loading
-6.74 KB
Loading

‎docs/gallery/gltf-skin-roundtrip/index.html‎

Lines changed: 599 additions & 245 deletions
Large diffs are not rendered by default.

‎docs/gallery/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,12 +662,12 @@ <h2><a href="triangulate-tangents/">triangulate-tangents</a></h2>
662662
</article>
663663
<article class="card" data-tags="export armature">
664664
<a class="card-media" href="gltf-skin-roundtrip/" tabindex="-1" aria-hidden="true">
665-
<img src="assets/gltf-skin-roundtrip-hero.webp" alt="Two blocky orange scorpion robots with curled tails and glowing cyan stingers, authored and re-imported." loading="lazy" decoding="async" />
665+
<img src="assets/gltf-skin-roundtrip-hero.webp" alt="Two orange-plated mech scorpions face off: the authored one coils its tail with claws tucked; the re-imported one, driven by its imported bones, rears its tail to strike with claws raised." loading="lazy" decoding="async" />
666666
</a>
667667
<div class="card-body">
668668
<h2><a href="gltf-skin-roundtrip/">gltf-skin-roundtrip</a></h2>
669669
<p class="teaches">A rigged mech scorpion exported to glTF with skins and re-imported, verifying the skinning contract the geometry round-trip left uncovered. Skeleton, weights, and deformation must all survive the format.</p>
670-
<p class="witnesses"><span class="tag">witnesses</span> skins[0].joints names every bone; JOINTS_0/WEIGHTS_0 per primitive with unit sums; weights bit-exact on re-import; deformation matches within 4.8e-07. The mesh must be parented to the armature or the exporter binds by name.</p>
670+
<p class="witnesses"><span class="tag">witnesses</span> skins[0].joints names all 21 bones; JOINTS_0/WEIGHTS_0 per primitive with unit sums; every split copy&#x27;s weights bit-exact on re-import; deformation matches within 6.0e-07. The mesh must be parented to the armature or the exporter binds by name.</p>
671671
<a class="card-link" href="gltf-skin-roundtrip/">View example<span class="sr-only"> gltf-skin-roundtrip</span> <span aria-hidden="true">&rarr;</span></a>
672672
</div>
673673
</article>

‎examples/gallery.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@
393393
"name": "gltf-skin-roundtrip",
394394
"dir": "examples/gltf-skin-roundtrip",
395395
"teaches": "A rigged mech scorpion exported to glTF with skins and re-imported, verifying the skinning contract the geometry round-trip left uncovered. Skeleton, weights, and deformation must all survive the format.",
396-
"alt": "Two blocky orange scorpion robots with curled tails and glowing cyan stingers, authored and re-imported.",
397-
"witnessesFix": "skins[0].joints names every bone; JOINTS_0/WEIGHTS_0 per primitive with unit sums; weights bit-exact on re-import; deformation matches within 4.8e-07. The mesh must be parented to the armature or the exporter binds by name.",
396+
"alt": "Two orange-plated mech scorpions face off: the authored one coils its tail with claws tucked; the re-imported one, driven by its imported bones, rears its tail to strike with claws raised.",
397+
"witnessesFix": "skins[0].joints names all 21 bones; JOINTS_0/WEIGHTS_0 per primitive with unit sums; every split copy's weights bit-exact on re-import; deformation matches within 6.0e-07. The mesh must be parented to the armature or the exporter binds by name.",
398398
"hero": "docs/gallery/assets/gltf-skin-roundtrip-hero.webp",
399399
"preview": "examples/gltf-skin-roundtrip/preview.webp",
400400
"tags": [

‎examples/gltf-skin-roundtrip/README.md‎

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# glTF Skin Round-Trip
22

3-
A runnable example that rigs a mech scorpion — seven-bone chain from pedestal
4-
root to stinger, blend rings at every joint seam — exports it with
3+
A runnable example that rigs a mech scorpion — 21 bones: a body, a
4+
five-segment tail and stinger, two three-bone claws (arm, hand, movable
5+
finger), eight legs; two-bone blends in the rubber boots at every flexing
6+
joint — exports it with
57
`bpy.ops.export_scene.gltf` (`export_skins=True`), parses the file, re-imports
68
it, and verifies the whole skinning contract against the authored rig,
79
following [`depsgraph-and-evaluated-data`](../../skills/depsgraph-and-evaluated-data/SKILL.md).
@@ -16,18 +18,22 @@ maps are in [`triangulate-tangents`](../triangulate-tangents/).
1618
round-trip left uncovered.
1719

1820
- **The skeleton survives.** `skins[0].joints` names every bone; the
19-
re-imported armature carries the same 7 bones, the same parent chain, and
20-
rest matrices within 2.4e-07 — the +Y-up conversion applies to bone nodes
21-
exactly as it does to meshes (their translations convert, no rotation is
22-
written).
21+
re-imported armature carries the same 21 bones, the same parent chain,
22+
and rest matrices within 2.3e-06 — the +Y-up conversion applies to bone
23+
nodes exactly as it does to meshes, whichever way a bone points (tail
24+
+Y, claws -Y, legs +/-X).
2325
- **The weights survive.** Every primitive carries JOINTS_0/WEIGHTS_0;
24-
per-vertex weights on disk sum to 1 (err 3.0e-08); the re-imported vertex
25-
groups match the authored groups **bit-exactly** (w_err 0.0), compared as
26-
straddle-safe position keys, the same protocol as the crate example.
26+
per-vertex weights on disk sum to 1 (err 3.0e-08; 840 disk vertices carry
27+
two influences); the re-imported vertex groups match the authored groups
28+
**bit-exactly** (w_err 0.0), compared as straddle-safe position keys, the
29+
same protocol as the crate example. Every split copy at a position is
30+
compared, not just one: the exporter splits vertices per normal, and each
31+
copy carries its own JOINTS_0/WEIGHTS_0, so one corrupted copy must not
32+
hide behind a good twin.
2733
- **The deformation survives.** Posed identically, the re-imported rig's
28-
evaluated mesh matches the original's within 4.8e-07 — linear blend
34+
evaluated mesh matches the original's within 6.0e-07 — linear blend
2935
skinning through the file format. The comparison is by rest-position key,
30-
never sorted multisets: the exporter welds duplicate loops (32 here), so
36+
never sorted multisets: the exporter welds duplicate loops (9960 here), so
3137
cardinalities differ and a naive sorted zip mispairs vertices (a phantom
3238
2.29 "deviation" measured and fixed during authoring).
3339
- **The mesh must be parented to the armature.** The exporter warns
@@ -36,21 +42,40 @@ round-trip left uncovered.
3642

3743
**What each check catches on failure:** exporting with `export_skins=False`
3844
(exit 5 — no skin on disk), stripping the weights (exit 4 — no vertex
39-
groups), and posing the re-imported rig differently (exit 19 — deformation
40-
deviates 0.90).
41-
42-
**Version witness:** the skins pipeline is stable between Blender 4.5 LTS and
43-
5.1 — the exporter/importer RNA is byte-identical (probed with
44-
`gltf-export-roundtrip`), and every measured value matches to the digit on
45-
4.5.11 and 5.1.2.
46-
47-
The render stages the authored scorpion beside the actual re-imported one —
48-
same curl, same glowing stinger — proof the skin rode the format through.
49-
Both rigs are turned three-quarters to the camera. The tail curls in the
50-
rig's own Y-Z plane, so a head-on view saw the curl end-on: the tail stood
51-
straight up behind the body like a chimney. The yaw is composed into each
52-
armature's world matrix, because the glTF importer leaves its armature in
53-
quaternion mode, where `rotation_euler` does nothing.
45+
groups), nudging one re-imported weight (exit 18 — 0.99), moving one
46+
re-imported bone's rest tail 1 mm (exit 14 — 3.9e-03), and posing the
47+
re-imported rig differently (exit 19 — deformation deviates 0.13).
48+
49+
**Straddle-safe keys, for real.** A float32 round-trip can land a
50+
coordinate on the far side of a 1e-4 rounding boundary, so lookups also try
51+
the 26 neighbouring keys. Those neighbours must themselves be rounded:
52+
`0.3601 + 1e-4` in binary floating point is not the key `0.3602`. The first
53+
version of this example added the offset without rounding, so only the
54+
exact key ever matched. It passed by luck until the redesign lowered the
55+
body to z = 0.36, and then it reported a phantom weight loss (exit 18, 1.0).
56+
57+
**Version witness:** the skins pipeline is stable across Blender 4.5 LTS,
58+
5.1 and 5.2 LTS — every measured value matches to the digit on 4.5.11,
59+
5.1.2 and 5.2.1.
60+
61+
The render is a standoff. On the left, the authored scorpion holds the
62+
guard pose the check compares: tail coiled tight over its back, claws
63+
tucked, pincers shut. On the right is the actual re-imported mesh, driven
64+
through its **imported** armature into a different pose: the tail rears
65+
high to strike, the claws are raised and the pincers gape. A file that lost
66+
the skin, the joints, or the weights could not follow that pose. The
67+
rubber boots at the joints bend smoothly and the plates stay rigid, which
68+
is the linear blend skinning surviving the format. The two poses differ on
69+
purpose, so the eye has something to compare. Two identical copies would
70+
render the same whether or not the round-trip worked.
71+
72+
Both rigs are posed by rest-space axes in quaternions, and the yaw is
73+
composed into each armature's world matrix, because the glTF importer
74+
leaves its armature in quaternion mode, where `rotation_euler` does
75+
nothing. Framing is measured by `gallery_framing` (fill 0.856 x, minimum
76+
margin 0.072). The asset floors are measured by `gallery_asset_quality`
77+
(5 materials: hazard paint, gunmetal, chrome, rubber, venom glow; edge90
78+
0.001).
5479

5580
## Run
5681

@@ -95,6 +120,11 @@ against it.
95120
| 19 | Deformation round-trip drifted |
96121
| 20 | `--output` produced no file |
97122

123+
On the `--output` path only, which runs after every check has passed, 10
124+
also means a `gallery_framing` violation and 11 a `gallery_asset_quality`
125+
floor violation. The shared helpers own those codes, and the log line says
126+
which one fired.
127+
98128
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
99129
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
100130
Smoke does not pass `--output` or `--no-skins`.

0 commit comments

Comments
 (0)