# calculate bone location/rotation from fcurve-animation-data

calculate bone location/rotation from fcurve-animation-data

I tried to understand the way the internal calculation works for the armature animation.
This is one first step,
blender-2.56 rev.34674
the calculation is not optimized, it is to compare the result with the internal blender calculation.
It only uses the fcurve-animation-data of the bones - no constraints etc.

pls. note every bug/wrong understanding and math.errors.

i tested it with an animation of bvh-data, thats why the name of the
test-armature is “BvhRig” in the example

``````
# what i found in blenkernel/intern/armature.c
#/* **************** The new & simple (but OK!) armature evaluation ********* */
#
#/*  ****************** And how it works! ****************************************
#
#  This is the bone transformation trick; they're hierarchical so each bone(b)
#  is in the coord system of bone(b-1):
#
#  arm_mat(b)= arm_mat(b-1) * yoffs(b-1) * d_root(b) * bone_mat(b)
#
#  -&gt; yoffs is just the y axis translation in parent's coord system
#  -&gt; d_root is the translation of the bone root, also in parent's coord system
#
#  pose_mat(b)= pose_mat(b-1) * yoffs(b-1) * d_root(b) * bone_mat(b) * chan_mat(b)
#
#  we then - in init deform - store the deform in chan_mat, such that:
#
#  pose_mat(b)= arm_mat(b) * chan_mat(b)
#
#  *************************************************************************** */
#

import bpy
from mathutils import *

def pretty_vector(v):
s = "%02.3f %02.3f %02.3f"%(v[0], v[1], v[2])
return(s)

def pretty(obj):
s = ""
for i in obj:
s += "%+01.2f "%(i)
return(s)

def pretty_m(m):
s = "euler "
s += pretty(m.to_euler())
if m.col_size == 4:
s += "  loc "
s += pretty(m.to_translation())
return(s)

# read the fcurves values dont works for frame-numbers ..
# how to get interpolated values ... ? in blender-2.49 there was a way, still missing in blender-2.5x?
# found it, use fcurve.evaluate(frame-number)
def get_action_location(action, bonename, frame=1):
#    print("loc for:", bonename)
loc = Vector()
if action == None:
return(loc)
data_path = 'pose.bones["%s"].location'%(bonename)
for fc in action.fcurves:
if fc.data_path == data_path:
#            loc[fc.array_index] = fc.keyframe_points[frame-1].co[1]
loc[fc.array_index] = fc.evaluate(frame)
return(loc)
def get_action_rotation(action, bonename, frame=1):
#    print("rot for:", bonename)
rot = Quaternion( (1, 0, 0, 0) )  #the default quat is not 0
if action == None:
return(rot)
data_path = 'pose.bones["%s"].rotation_quaternion'%(bonename)
for fc in action.fcurves:
if fc.data_path == data_path: # and frame &gt; 0 and frame-1 &lt;= len(fc.keyframe_points):
#            print("rot", fc.array_index, fc.keyframe_points[frame-1].co[1])
#            rot[fc.array_index] = fc.keyframe_points[frame-1].co[1]
rot[fc.array_index] = fc.evaluate(frame)
return(rot)

# return the matrix for a bone for the fcurves animation and object - without scale or constraints ..
# armature-object, name of the bone, possible action, frame-number for action-keyframes, trace-values-output
def mybone(armature, bonename, action=None, frame=1, debug=-2):
do_print = False
if debug == -1: do_print = True  #print all for -1 .. or only for the bone-number in parent-chain
parents=[]
parents.append(bonename) # append the bone itself
b = bonename
while armature.pose.bones[b].parent: #is there a parent bone? loop till top-parent reached
b = armature.pose.bones[b].parent.name #get parents-name
parents.append(b) #and append the bone-name
parents.reverse() # reverse to start with top-parent and work down to the child
if do_print: print(parents)
new_matrix = {}
matrix_world = armature.matrix_world.copy() #use the armature-object matrix offset?
if do_print: print("world:", pretty_m(matrix_world))
for i,b in enumerate(parents):
if do_print or debug == i: print(i, b)
posematrix = armature.pose.bones[b].matrix.copy() #use it for comparator check, should be no more used if it works
if do_print or debug == i: print("matrix:____________", pretty_m(posematrix))
m = get_action_rotation(action, b, frame) #read the fcurve-animation rotation
m = m.to_matrix().to_4x4()
l = get_action_location(action, b, frame) #read the fcurve-animation location
if do_print or debug == i: print("from anim-data:", pretty_m(Matrix.Translation(l)*m) )
local = armature.data.bones[b].matrix_local.copy() # get the armatures bone matrix_local - restpose
if do_print or debug == i: print("local", pretty_m(local))
if i &gt; 0: # there is a parent, use the parents space coord-system
# the parents current animation-setting
pm = new_matrix[parents[i-1]] #parents current matrix
# parents-pose-matrix subtracted with its local-matrix - to this bones local-matrix
# undo/subtract a matrix operation ist multiplication with its inverted
pm = pm * armature.data.bones[parents[i-1]].matrix_local.inverted() * local
# add this bones animation-data ...
m = pm * m
else: #no parent, the only one i need is the local-matrix and the animation-data
m = local * m  # i dont get, why this is different compared with child-bones-procedure
l = l * local  # with root-bone rotation and translation, build the full matrix
m = Matrix.Translation(l) * m # translate the rotation-matrix .. to the head position
if do_print or debug == i: print("matrix:            ",pretty_m(m) )
new_matrix[b] = m # store the current animation-setting .. for the child-bones
return(matrix_world * m)

# now run the test for this armature, its action and the bone-name at the framenumbers
armature="BvhRig"
dbg=-2
frame=30

for frame in range(20, 30, 2):
bpy.context.scene.frame_set(frame)
m = mybone(bpy.data.objects[armature], bone, bpy.data.objects[armature].animation_data.action, frame, dbg)
# this prints the pose-matrix of the bone without the armature-animation-offest
print("original:",bone, pretty_m(bpy.data.objects[armature].pose.bones[bone].matrix))
# this prints the python-calculated bone-animation with armature-offset
# if the armature is not rotated or moved, then it should be the same (without constraints .. or scaling)
print("calculat:", bone, pretty_m(m))

``````

today the second part,
its based on the first part,
which i needed to understand and then to lookup
how to transpose a action to an armature with
a different restpose. This is only a restpose-change
in rotations, not locations or bone-sizes.
It might be of use for those recorded actions, where one
need to use a different restpose to attach to a already made mesh.
Imagine having a source with different actions done for different
restpose-settings (human-armature in start-pose and different relaxed ones)

``````
#transfer action, same both actions must exist, to armature with different restpose (only rotation-differences)
def transfer_action(a1, a2, action_source, action_dest):
#first source armature, second armature with different restpose only rotation!
#then the two actions, you have to make a copy before
#first try only the rotations, what if some missing - i need all 4 of a quaternion
print(action_source.name, "dest:", action_dest.name)
#for several tries allways copy the source-action-entries again
for f1, f2 in zip(action_source.fcurves, action_dest.fcurves):
for i in range(len (f1.keyframe_points)):
#break
f2.keyframe_points[i].co[1] = f1.keyframe_points[i].co[1]
fcbones = {}
#collect the groups of action-fcurves
for fc in action_dest.fcurves:
data_path = fc.data_path
if data_path in fcbones:
fcbones[data_path][fc.array_index] = fc
else:
fcbones[data_path] = [-1, -1, -1, -1]
fcbones[data_path][fc.array_index] = fc
#    print(fcbones)
for k in (fcbones.keys()):
if "quaternion" in k:
bonename = k.split('"')[1] #should be 3 parts with bonename in ".."
print(bonename)
local_new = a2.data.bones[bonename].matrix_local.to_3x3()
local_old = a1.data.bones[bonename].matrix_local.to_3x3()
if a2.pose.bones[bonename].parent: #for a parent, use its changes too
pname = a2.pose.bones[bonename].parent.name
local_pnew = a2.data.bones[pname].matrix_local.to_3x3()
local_pold = a1.data.bones[pname].matrix_local.to_3x3()
ll = local_new.inverted() * local_pnew * local_pold.inverted() * local_old
else:
ll = local_new.inverted() * local_old
#loop over all keyframes
fcs = fcbones[k]
for i in range( len (fcs[0].keyframe_points) ):
q = Quaternion()
for j in range(4):
q[j] = fcs[j].keyframe_points[i].co[1]
qnew = (ll * q.to_matrix() ).to_quaternion()
#print(pretty(q), pretty(qnew))
for j in range(4):
fcs[j].keyframe_points[i].co[1] = qnew[j]

# now run the test for this armatures, and its actions
armature="BvhRig.001"  #source-armature
a2 = "BvhRig"  #destination-armature with same copy of action, but different restpose

transfer_action(bpy.data.objects[armature], bpy.data.objects[a2],
bpy.data.objects[armature].animation_data.action, bpy.data.objects[a2].animation_data.action)

``````

its strange, no one appreciated that code,
thanks, its amazing and very helpful