graft --bind=part: fix the dangling rebind, but the bind-pose mismatch remains UNSOLVED

Instrumented the graft stage by stage on ultra rather than guessing further. Measured:
  mesh AFTER T    : (-0.434 .. -0.352)  <- correct, anchor was -0.393. The transform was always right.
  mesh AFTER join : (-0.024 ..  0.002)  <- snapped to origin
  mesh parent now : Armature   mods: [('ARMATURE', None)]

So bpy.ops.object.join() deletes the part's armature OBJECT, leaves the part mesh's ARMATURE
modifier pointing at None, and re-parents the mesh to whatever empty sat above it (Sketchfab
exports nest several). Fixed by saving each part mesh's world matrix before the join, then
re-binding the modifier to the body armature and restoring the placement.

That fixes the PLACEMENT — the hand now lands on the wrist — but exposes the real problem
underneath, which is NOT fixed: the hand mesh is violently stretched. The body armature carries
object scale 0.01 and the hand rig 1.0, so joining bakes the hand bones into the body's space
while the mesh's bind pose still expects the original bone positions. The armature modifier then
deforms it from a bind pose that no longer matches. Restoring the object matrix cannot fix this:
on a skinned mesh the bones drive the geometry, not the object transform.

STATUS, stated plainly:
  --bind=body  WORKS. Detailed geometry on the existing rig, no finger articulation.
  --bind=part  places correctly, deforms wrongly. DO NOT USE.

The better route, and the one to take given John is happy to re-rig: graft the hand geometry with
--bind=body, then send the combined mesh back through Mixamo for a fresh full auto-rig. That
sidesteps the cross-armature rebind entirely, lets the tool that is actually good at binding do
the binding, and returns mixamorig-NAMED finger bones — which the existing clip bank can drive,
unlike this hand's _rootJoint/thumb_base.R_03 naming, which nothing in the bank references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-25 00:59:28 +10:00
parent c55d7c5b8d
commit d6d69189ed

View File

@ -332,11 +332,26 @@ elif OP == 'graft':
raise SystemExit(f'part rig shares {len(clash)} bone name(s) with the body '
f'({sorted(clash)[:3]}…). Joining would silently repoint weights — '
f'use --bind=body instead.')
# join() deletes the part's armature OBJECT. Measured consequence: the part mesh's
# ARMATURE modifier is left pointing at None and the mesh gets re-parented to whatever
# empty sat above it (Sketchfab exports nest one), so it collapses from the wrist back to
# rest position at the origin. Save the placement and re-bind explicitly afterwards.
placed = {g.name: g.matrix_world.copy() for g in part_meshes}
bpy.ops.object.select_all(action='DESELECT')
parm.select_set(True); arm.select_set(True)
bpy.context.view_layer.objects.active = arm # active survives the join
bpy.ops.object.join()
bpy.context.view_layer.update()
for name, wm in placed.items():
g = bpy.data.objects.get(name)
if not g:
continue
for m in g.modifiers:
if m.type == 'ARMATURE' and m.object is None:
m.object = arm # re-bind the dangling modifier
g.parent = arm
g.matrix_world = wm # restore the placement join destroyed
bpy.context.view_layer.update()
bpy.context.view_layer.objects.active = arm
bpy.ops.object.mode_set(mode='EDIT')
eb = arm.data.edit_bones