1
$\begingroup$

I have some animation fcurve data provided for the case of inherit scale unchecked for pose bones. Example: Bone03 is a child of Bone02. Inherit scale is unchecked for Bone03. At frame 1, Bone02 is not scaled (i.e. Sx, Sy, Sz = 1.0000). At frame 30, Bone02 is scaled on Y with a factor of 2 (Sy = 2.0000; Sx, Sz = 1.0000). Since inherit scale is not checked for Bone03, it is not affected by it's parent scaling at frame 30. This is the intended result.
Problem: I need to import the blend file in Jmonkey which scales the child regardless of it's setting in Blender. The animation hence appears scaled wrongly in Jmonkey. Basically, I need to check inherit scale on the child bone and then reverse the inherit scale operation on the child bone via a python script in Blender before loading it in Jmonkey.

I've tried to copy the matrix of the child pose bone (before setting use_inherit_scale to True on the child edit bone) and then reassign it to the child pose bone. The result looks like it ignores the reassign of the original matrix and scales the child anyway. Here's the code:

import bpy

obj = bpy.context.object
scene = bpy.context.scene
bpy.ops.object.mode_set(mode='POSE')
scene.frame_set(30.0)

pb = obj.pose.bones['Bone03']
start_matrix = pb.matrix.copy()  #copy the initial pose bone matrix 
print(start_matrix)

bpy.ops.object.mode_set(mode='EDIT')
eb = obj.data.edit_bones['Bone03']
eb.use_inherit_scale = True

bpy.ops.object.mode_set(mode='POSE')
print(pb.matrix)
pb.matrix = start_matrix  #try to force reassign the start matrix

print(pb.matrix)

Here is a file with the problem demonstrated (I hope much clearer). When you open it the animation is correct (but with inherit scale unchecked). As soon as you run the script, Bone03 get's scaled wrongly.
The question: how can I force reassign the initial matrix to the pose bone, after programmatically setting use_inherit_scale = True on the edit bone?

$\endgroup$
2
$\begingroup$

Just a short draft. I might expand this answer later.

import bpy
from mathutils import Matrix, Vector

# for debugging purposes - visualize transforms
def create_empty(name, matrix):
    if name not in bpy.data.objects:
        empty = bpy.data.objects.new(name, None)      
        scene.objects.link(empty)
    else:
        empty = bpy.data.objects[name]

    empty.empty_draw_type = "ARROWS"
    empty.empty_draw_size = 1.0
    empty.show_x_ray = True
    empty.show_name = True

    empty.matrix_basis = matrix

# ported from blenkernel/intern/armature.c to python
# --------------------------------------------------------------------
def get_mat_offs(bone):        
    mat_offs = bone.matrix.to_4x4()
    mat_offs.translation = bone.head
    mat_offs.translation.y += bone.parent.length

    return mat_offs

def get_mat_rest(pose_bone, mat_pose_parent):
    bone = pose_bone.bone

    if pose_bone.parent:
        mat_offs = get_mat_offs(bone)

        # --------- rotscale       
        if (not bone.use_inherit_rotation and 
            not bone.use_inherit_scale):                
            mat_rotscale = bone.parent.matrix_local * mat_offs

        elif not bone.use_inherit_rotation:                        
            mat_size = Matrix.Identity(4)
            for i in range(3):
                mat_size[i][i] = mat_pose_parent.col[i].magnitude
            mat_rotscale = mat_size * bone.parent.matrix_local * mat_offs

        elif not bone.use_inherit_scale:
            mat_rotscale = mat_pose_parent.normalized() * mat_offs

        else:
            mat_rotscale = mat_pose_parent * mat_offs

        # --------- location     
        if not bone.use_local_location:
            mat_a = Matrix.Translation(
                mat_pose_parent * mat_offs.translation)

            mat_b = mat_pose_parent.copy()
            mat_b.translation = Vector()

            mat_loc = mat_a * mat_b

        elif (not bone.use_inherit_rotation or 
              not bone.use_inherit_scale):                  
            mat_loc = mat_pose_parent * mat_offs

        else:
            mat_loc = mat_rotscale.copy()

    else:    
        mat_rotscale = bone.matrix_local
        if not bone.use_local_location:
            mat_loc = Matrix.Translation(bone.matrix_local.translation)
        else:
            mat_loc = mat_rotscale.copy()      

    return mat_rotscale, mat_loc

def get_mat_pose(pose_bone, mat_pose_parent=None, mat_basis=None):
    if pose_bone.parent and not mat_pose_parent:
        mat_pose_parent = pose_bone.parent.matrix
    if not mat_basis:
        mat_basis = pose_bone.matrix_basis

    mat_rotscale, mat_loc = get_mat_rest(pose_bone, mat_pose_parent)    
    mat_pose = mat_rotscale * mat_basis
    mat_pose.translation = mat_loc * mat_basis.translation

    return mat_pose    
# --------------------------------------------------------------------

def adjust_matrix(pose_bone, mat_parent_old, mat_parent_new, preview=True):                    
    mat_pose_old = get_mat_pose(pose_bone, mat_parent_old)
    mat_rotscale_old, mat_loc_old = get_mat_rest(pose_bone, mat_parent_old)    
    pose_bone.bone.use_inherit_scale = True        
    mat_rotscale_new, mat_loc_new = get_mat_rest(pose_bone, mat_parent_new)

    mat_fix_rotscale = mat_rotscale_new.inverted() * mat_rotscale_old
    mat_fix_loc = mat_loc_new.inverted() * mat_loc_old 

    mat_basis_old = pose_bone.matrix_basis
    mat_basis_new = mat_fix_rotscale * mat_basis_old
    mat_basis_new.translation = mat_fix_loc * mat_basis_old.translation

    mat_pose_new = get_mat_pose(
        pose_bone,
        mat_parent_new,
        mat_basis_new)

    if preview:
        create_empty(pose_bone.name, mat_pose_new)  
    else:
        pose_bone.matrix_basis = mat_basis_new

    return mat_pose_old, mat_pose_new

def apply_change(parent, mat_parent_old, mat_parent_new):
    for child in parent.children:
        mat_old, mat_new = adjust_matrix(
            child, 
            mat_parent_old, 
            mat_parent_new,
            preview=True
        )                       
        apply_change(child, mat_old, mat_new)

if __name__ == "__main__":
    scene = bpy.context.scene
    obj = bpy.data.objects['Armature']
    roots = [bone for bone in obj.pose.bones if not bone.parent]

    for root in roots:
        create_empty(root.name, root.matrix)
        apply_change(root, root.matrix, root.matrix)

    obj.update_tag({'DATA'})
    scene.update()
$\endgroup$
  • $\begingroup$ I've adapted the code to my file (Bone03 as Child and Bone02 as Parent), but the result is the same. Here is the new file (just click Run Script): drive.google.com/file/d/0ByYs2Oos-zyKY1E3WkU1VlhKUXM/… $\endgroup$ – Vlad Balan Oct 13 '15 at 15:52
  • $\begingroup$ Updated the answer. Still a draft. $\endgroup$ – pink vertex Oct 21 '15 at 22:14
  • $\begingroup$ Another update. There seems to be a bug with assignments to matrix_basis and non-uniformly scaled bones. Somehow the rotation is not correctly extracted from the matrix. $\endgroup$ – pink vertex Oct 27 '15 at 23:10
  • $\begingroup$ Not a bug. The non-uniform scale causes deformations of the axes. To compensate for that the new matrix_basis may be non-orthogonal and can not be decomposed into loc, rot, scale. Thus you would have to export the matrices or adjust the scaling of the bones to uniform scaling. $\endgroup$ – pink vertex Oct 30 '15 at 10:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.