How to?: global pose transforms to hierarchial Armature?

I’m trying this one more time and have done some more tests, the issue is more clear now.

I have some matrices I need to assign to PoseBones. The resulting pose looks fine when there is no bone hierarchy (parenting) and messed up when there is.

I’ve got an example animation importer addon, an example animation, as well as an example blend file with Armatures the animations should be loaded on.
That should hopefully be enough.
http://www.2shared.com/file/5qUjmnIs/sample_files.html

Import the animation by selecting an Armature and running the importer on “sba” file.

Do this for both Armatures.


See the problem?

I’ve tried checking if bone has parent and then multiplying the animation matrices from the files with the inverse of the bone’s parent’s matrix, that didn’t work.

For the record, in the old 2.4 API this worked like a charm:


matrix_basis = ... # matrix from file
animation_matrix = armature.bones['mybone'].matrix['ARMATURESPACE'].copy() * matrix_basis
pose.bones[bonename].poseMatrix = animation_matrix

pose.update()

<moved contents to first post>

I’ve posted everything possible…

come on guys :frowning:

I just noticed that assigning an animation matrix to a PoseBone changes the matrix. What?


&lt;Matrix 4x4 (-0.0147,  0.0409, -0.9991,  0.3136)
            ( 0.0093,  0.9991,  0.0407, -0.0768)
            ( 0.9998, -0.0087, -0.0150,  9.2912)
            ( 0.0000,  0.0000,  0.0000,  1.0000)&gt;
&lt;Matrix 4x4 (-0.0000, -0.0000, -1.0000, -0.0000)
            (-0.0000,  1.0000, -0.0000, -0.1500)
            ( 1.0000,  0.0000, -0.0000,  9.3372)
            ( 0.0000,  0.0000,  0.0000,  1.0000)&gt;

from file/in PoseBone

I just remembered matrix.decompose(). So maybe anyone knows how to calculate a local postion, location and scale vectors given global ones instead?

Looks like a double rotation. Posebone.matrix is in object(armature) space. Posebone.matrix rotation rotates parent bone.vector into child bone.vector. If calculating rotation from armature up vector you will get a double rotation (calculated rotation and parent rotation). When this double rotation is animated it looks like rotational acceleration.

EDIT: Posebone.matrix is in bonespace. Posebone.matrix rotation rotates armature up vector into bone vector.

I’m not sure if I really get what this double rotation is. if you mean the parent rotation gets added to child, then I’ve already tried multiplying the bone matrix by parent bone matrix inverse. But in that case position and scale would be relative too, so likely not what you mean?

Could you clarify? And if you know of a solution please tell me that too.

Correct, parent rotation + your rotation.

From your picture the top bone is way off because the extra rotations are cumulative.

I’m using 2.62, I’m using Posebone.matrix. Posebone.matrix is in object(armature) space.

Solution:

Read in matrix from file.
Calculate bone vector(armature space).
Calculate rotation from parent bone vector to bone vector.
EDIT: Calculate rotation from armature up vector to bone vector .
EDIT: Transform rotation to bonespace
Calculate bone head(armature space)
Assign rotation and location to your Posebone.matrix.
No inverse matrix needed.
No bone space transformation needed.

EDIT: bonespace transform IS needed.

In 2.62 armature API there are many different types of matrices, try to find one that will avoid using inverses.

From your picture the top bone is way off because the extra rotations are cumulative.

Could you explain why that’s the case for rotations, but not locations? Positions of the bones are still set in armature-space if I just assign the matrix. I don’t get it, that’s not how relative spaces work from my previous experiences. Any ideas?

Calculate bone vector(armature space).
Calculate rotation from parent bone vector to bone vector.
Calculate bone head(armature space)
Assign rotation and location to your Posebone.matrix.

ok…

Calculate rotation from parent bone vector to bone vector.

How?

Assign rotation and location to your Posebone.matrix.

By location you mean the bone head position, right?

Why?

In 2.62, with Posebone.matrix, Location is from object space (armature) origin. Rotation is from parent bone vector to bone vector. This is what Blender expects.

EDIT: Rotation is from armature up vector to bone vector.

Poses are relative to rest pose. This relative space is handled internally by Blender. If you use Posebone.matrix you do not have to calculate the relative space you only have to calculate the rotation from current parent vector to current bone vector. Rest pose vectors are not used in your calculations.

Edit bones represent the rest pose. When importing, if you build your armature at origin then your armature space coincides with world space and global coordinates coincide with local coordinates. No relative spaces needed when using Posebone.matrix .

How?
There are blender math functions that will produce a rotation given two vectors.

Bone head?
Yes location is the bone head position in armature space.

There are blender math functions that will produce a rotation given two vectors.
Which one? This one?

vector.rotation_difference(other_vector)

http://www.blender.org/documentation/blender_python_api_2_63_5/mathutils.html

And forgot this

Calculate bone vector(armature space).
bone vector = bone tip - bone head right? I think thats all.

EDIT: this isnt right


                # Calculate bone vector(armature space).
                pos = matrix.to_translation()
                axis = matrix.to_3x3().col[1]
                head = pos
                tail = pos + axis
                vector = tail - head
                
                # Calculate rotation from parent bone vector to bone vector
                parent_vector = pose.bones[bonename].parent.vector
                rotation = parent_vector.rotation_difference(vector)
                
                # Calculate bone head(armature space)
                head = head
                
                # Assign rotation and location to your Posebone.matrix.
                pose.bones[bonename].location = head
                pose.bones[bonename].rotation_quaternion = rotation

Yes.

If import works without parenting then import animations without parenting to get correct bone vectors. Then do calculations for parenting using correct bone vectors.

I rather not import something twice just for that.

Did you see the code I posted as an edit?

Your code should work if your import calculations with the animation matrix give you the correct vectors.

What?

Here’s the whole updated addon, replace it with the old one and import it on both Armatures in the test blend.


bl_info = {
    'name':         'SBA simple binary animation format',
    'author':       'vida_vida',
    'version':      (0, 1, 1),
    'blender':      (2, 6, 2),
    'location':     'File &gt; Import &gt; SBA animation (.sba)',
    'description':  'SBA animation importer',
    'category':     'Import'}

import bpy
import mathutils
from math import radians
from bpy.props import *
from bpy_extras.io_utils import ExportHelper, ImportHelper

import os
import struct

def importSBA(filepath):
    name = os.path.basename(filepath)
    realpath = os.path.realpath(os.path.expanduser(filepath))
    
    fileobject = open(realpath, 'rb')
    
    armobj = bpy.context.scene.objects.active
    if not armobj or armobj.type != 'ARMATURE':
        raise Exception('An Armature must be selected!')
        return
    
    pose = armobj.pose
    
    armobj.animation_data_create()
    
    actionname = bpy.path.display_name_from_filepath(filepath)
    action = bpy.data.actions.new(name = actionname)
    armobj.animation_data.action = action
    
    fileobject = open(filepath, 'rb')
    
    # &lt;char*4&gt; signature (version)
    datachunk = fileobject.read(4)
    if datachunk != b'TSB0':
        fileobject.close()
        raise Exception('This file is not a valid SBA file!')
        return
        
    # &lt;int*3&gt; zero
    datachunk = fileobject.seek(12, 1)
    
    # &lt;int&gt; number of bones
    bonecount = struct.unpack('&lt;I', fileobject.read(4))[0]
    
    # &lt;int&gt; number of frames 
    framecount = struct.unpack('&lt;I', fileobject.read(4))[0]
    
    # &lt;int&gt; unused
    fileobject.seek(4, 1)
    
    # &lt;int&gt; unused
    datachunk = fileobject.seek(4, 1)
    
    # We need to assign new pos/rot/scale key to each bone, for each frame.
        
    # we read each animation matrix for each (pose)bone, for each frame from the SBA file and store the data in lists
    
    frames = [] # 'frames' list contains 'frame' sublists itself, the amount of them are equal to the amount of frames, they in turn contain the matrices for each bone
    
    for i in range(framecount):
        frames.append([])
        
    #for each frame:
    #   for each bone:
    #       # &lt;float*16&gt; transform matrix
    
    # this will make the sublists have all the transform matrices as their members
    for frameindex in range(len(frames)):
        for x in sorted(pose.bones.keys()):
            rawlist = struct.unpack('&lt;16f', fileobject.read(64))
            
            amat = mathutils.Matrix.Rotation(radians(90),4,'X') * mathutils.Matrix(( # since 2.62 a list of rows is supplied, not list of columns
                (rawlist[0], rawlist[4], rawlist[8], rawlist[12]),
                (rawlist[1], rawlist[5], rawlist[9], rawlist[13]),
                (rawlist[2], rawlist[6], rawlist[10],rawlist[14]),
                (rawlist[3], rawlist[7], rawlist[11],rawlist[15])))
            
            frames[frameindex].append(amat)
            
    # finally, add keys based on the data from the huge 'frames' nested list
    bonenumber = 0
    
    # for each frame:
    for frame in range(framecount):
        # for each pose bone: add a key
        for bonename in sorted(pose.bones.keys()):
            if pose.bones[bonename].parent:
                
                matrix = frames[frame][bonenumber]
                
                # Calculate bone vector(armature space).
                pos = matrix.to_translation()
                axis = matrix.to_3x3().col[1]
                head = pos
                tail = pos + axis
                vector = tail - head
                
                # Calculate rotation from parent bone vector to bone vector
                parent_vector = pose.bones[bonename].parent.vector
                rotation = parent_vector.rotation_difference(vector)
                
                # Calculate bone head(armature space)
                head = head
                
                # Assign rotation and location to your Posebone.matrix.
                pose.bones[bonename].location = head
                pose.bones[bonename].rotation_quaternion = rotation
                
                
            else:
                pose.bones[bonename].matrix = frames[frame][bonenumber]
                
            # create the 'keys' for the Action from the poses
            pose.bones[bonename].keyframe_insert('location', frame = frame+1)
            pose.bones[bonename].keyframe_insert('rotation_quaternion', frame = frame+1)
            pose.bones[bonename].keyframe_insert('scale', frame = frame+1)
            
            bonenumber += 1
            
        bonenumber = 0
                    
# GUI part
class ImportSBA(bpy.types.Operator, ImportHelper):
    bl_idname= 'import_scene.sba'
    bl_description = 'Import SBA animation (.sba)'
    bl_label = 'Import SBA'
    bl_options = {'INTERNAL'} # not write steps to Blender History (Ctr+Z)
    filename_ext = '.sba'
    filter_glob = StringProperty(default = '*.sba', options={'HIDDEN'})
    
    filepath = StringProperty(name = 'File Path', description = 'Filepath used for importing the SBA file', maxlen = 1024, default = '')
    
    def execute(self, context): 
        importSBA(self.properties.filepath)
        return {'FINISHED'}
        
    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}
        
def menu_func(self, context):
    self.layout.operator(ImportSBA.bl_idname, text = 'SBA animation (.sba)')

def register():
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_file_import.append(menu_func)

def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.INFO_MT_file_import.remove(menu_func)

if __name__ == '__main__':
    register()

Not right.

Try using Posebone.matrix, you can convert your quaternion using toMatrix() resizeto4x4 and add your location then assign.

If that doesn’t work check your calculated vectors against the good vectors from the picture of your first post.

Try using Posebone.matrix, you can convert your quaternion using toMatrix() resizeto4x4 and add your location then assign.

what? .

Put your rotation and location in a matrix and assign to pose.bone.matrix .

Don’t use pose.bone.location or pose.bone.rotation_quaternion.

Keep all of your keyframe_inserts.

Oh. dont know how to do the reverse of matrix.decompose(). How to generate a matrix from those values?

Either way I don’t see the difference in assigning matrix instead. But I’ll try.

Quaternion.to_matrix() gives you a 3x3 matrix.

Resize the matrix to 4x4.

Mat[0][3] = loc.x
Mat[1][3] = loc.y
Mat[2][3] = loc.z

Is that right?