2
$\begingroup$

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?

$\endgroup$

1 Answer 1

1
$\begingroup$

After thinking about it some more, I came to the conclusion that there is simply no straightforward way to reverse deformation. Since deformation of a vertex is calculated as a function of its initial location, once that is lost, and as long as we have more than one bone acting on the vertex, the inverse relationship is going to be highly nontrivial.

So, I had to get extra creative. Here's my approach, if anyone ever needs something along these lines:

https://github.com/veryfancypants/hs2_blender_export/blob/ee8e833c1c286b3f105d5f6ae39b5f693ffc3ef4/meshexporter.py#L764

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .