I am working on a Blender script that imports characters from a video game. Without going into too much detail, my starting point is a package produced by a game plugin that includes a weight-painted, deformed mesh, and a full rig that describes the deformation (one 4x4 matrix for each bone). This "baked-in" deformation describes the character's physical dimensions, and it is mostly done through scales (e.g, to make a character with thick forearms, they might apply scale (1.2,1.2,1.0) to the bone that deforms the forearms.) I can then write the matrices into bpy.context.object.data.edit_bones['name'].matrix and I get a nicely rigged character that I can do FK on.
This part works well enough. But now I want to do more 'interesting' things like edit the character's dimensions or morph between characters, so I need to be able to revert the mesh to its 'original' undeformed shape. Getting the undeformed mesh straight out of the game is a last resort option, for various reasons. That's where I hit a snag. My first expectation was that I could just go into pose mode, apply the inverse of each bone's scaling to bpy.data.objects['Armature'].pose.bones['name'].matrix_basis, and I would get the original mesh. But it apparently does not work like that. It's close enough for scales near 1, but, if deformations are large, I see large deviations.
I can't find the deformation formulas documented anywhere and I don't want to start digging through Blender's source code, but I tried an experiment. Take a cube, insert two bones, parent with automatic weights, "pose mode", scale one bone by a factor of 0.2 (which turns the cube into a prism), "object mode", "apply visual transformation to mesh" on the cube, back to pose mode on the armature, "apply pose as rest pose", parent them again (this time, with armature but without automatic weights), and, in fact, I can't turn the prism back into a cube any more. So, this deformation is irreversible? Or some information is lost along the way?
My last attempt was, instead of loading the initial deformation into the EditBone layer, to split up each matrix into the EditBone component that gives me the default character, and the PoseBone component with scales and offsets that take me from the default character to the custom character. Then the challenge is that I can't modify PoseBone objects without deforming the mesh. Even if I remove the armature modifier from the mesh, write into PoseBones, then reparent, the mesh deforms the moment I reparent.
I found this How do i Parent a mesh to an armature that is not in rest Pose? and this Apply as rest pose deforms the animation, neither of those options worked exactly right, but I managed to come up with some really hacky code that gets me about halfway:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = arm
bpy.ops.object.mode_set(mode='EDIT')
for x in bpy.context.object.data.edit_bones:
x.matrix=default_armature[x.name]
# unparent the mesh from the armature
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
body.select_set(True)
bpy.context.view_layer.objects.active = body
bpy.ops.object.parent_clear(type='CLEAR')
# recursively set matrix_basis to map from the default armature to the customized armature
bpy.context.view_layer.objects.active = arm
bpy.ops.object.mode_set(mode='POSE')
setup_pose_one_bone('cf_J_Root', arm)
# create a duplicate of the armature
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
arm.select_set(True)
bpy.ops.object.duplicate()
ob = bpy.context.object
# on 'arm', set current pose as rest pose
bpy.context.view_layer.objects.active = arm
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.pose.armature_apply()
# parent the mesh to 'arm'
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
body.select_set(True)
arm.select_set(True)
bpy.context.view_layer.objects.active = arm
bpy.ops.object.parent_set(type='ARMATURE', keep_transform=False)
# constrain each bone of 'arm' to exactly match the corresponding bone of 'ob'
bpy.ops.object.mode_set(mode='POSE')
for x in arm.pose.bones:
con = x.constraints.new('COPY_TRANSFORMS')
con.target=ob
con.subtarget=x.name
Now I have two armatures, the mesh is parented to one, but controlled by the other. All the bones are in the right places, and, if I put the second armature into rest position, the mesh is resized to the dimensions of the reference mesh. But something is still wrong, because applying constraints deforms the mesh slightly out of shape. I don't see a clear way forward here either.
Any suggestions?