From d6d69189ed5a30725dd1256df3883ecfbdc4ed25 Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 25 Jul 2026 00:59:28 +1000 Subject: [PATCH] graft --bind=part: fix the dangling rebind, but the bind-pose mismatch remains UNSOLVED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- blender_ops.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/blender_ops.py b/blender_ops.py index a768a1e..b75cbdf 100644 --- a/blender_ops.py +++ b/blender_ops.py @@ -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