# Calculating the correct roll from a bone transform matrix

I'm trying to get my Blender3d modeller importer to create correct bones from my file's transform matrices.

The Blender Python API doesn't have a way to do this (anymore!), so people had to dig the Blender C source to find out how Blender does it and port that. Here's a Python port of the needed functions, not by me (I don't know C):

def vec_roll_to_mat3(vec, roll):
target = mathutils.Vector((0,1,0))
nor = vec.normalized()
axis = target.cross(nor)
if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix
axis.normalize()
theta = target.angle(nor)
bMatrix = mathutils.Matrix.Rotation(theta, 3, axis)
else:
updown = 1 if target.dot(nor) > 0 else -1
bMatrix = mathutils.Matrix.Scale(updown, 3)
rMatrix = mathutils.Matrix.Rotation(roll, 3, nor)
mat = rMatrix * bMatrix
return mat

def mat3_to_vec_roll(mat):
vec = mat.col[1]
vecmat = vec_roll_to_mat3(mat.col[1], 0)
vecmatinv = vecmat.inverted()
rollmat = vecmatinv * mat
roll = math.atan2(rollmat[0][2], rollmat[2][2])
return vec, roll


How to use them:

pos = mymatrix.to_translation()
axis, roll = mat3_to_vec_roll(mymatrix.to_3x3())

bone = armature.edit_bones.new('name')
bone.tail = pos + axis
bone.roll = roll


Sadly, it seems even the Blender C code is buggy and doesn't take into account cases when the bone is parallel to (0,1,0). So such bones can get assigned wrong (by 180 degrees) rolls and mess up animations.

Does anyone have better code for generating roll which takes into account such cases?

The old Blender 2.4 API generated correct ones. Its C code can be found here: http://svn.blender.org/svnroot/bf-blender/branches/blender-2.47/source/blender/blenkernel/intern/armature.c I'm no C coder myself, again.

• So you're asking that someone who knows C and Python to convert the C code to Python? – MichaelHouse Jul 18 '12 at 14:45
• No, that would be one of the few ways to answer this (but only if the C functions in 2.4 are different from the C functions in 2.6 which were ported, which seems to be so since my Blender 2.4 importer works fine). Other ways to answer would be to explain in words what needs to be done, or put together some pseudocde, or modify the existing Pythin functions to work for this scenario. – user17402 Jul 18 '12 at 16:59

For anyone looking for a solution to this problem, I'm posting this wherever I found it referred to. I have ported this newer upated internal C code of blender into python. I have not extensively tested it, but it seems correct, produces good results and it was super easy to port. It is used in the exact same way as before (see the above question), should be more accurate and get all (?) bone orientations right!

def vec_roll_to_mat3(vec, roll):
#port of the updated C function from armature.c
#https://developer.blender.org/T39470
#note that C accesses columns first, so all matrix indices are swapped compared to the C version

nor = vec.normalized()
THETA_THRESHOLD_NEGY = 1.0e-9
THETA_THRESHOLD_NEGY_CLOSE = 1.0e-5

#create a 3x3 matrix
bMatrix = mathutils.Matrix().to_3x3()

theta = 1.0 + nor[1];

if (theta > THETA_THRESHOLD_NEGY_CLOSE) or ((nor[0] or nor[2]) and theta > THETA_THRESHOLD_NEGY):

bMatrix[1][0] = -nor[0];
bMatrix[0][1] = nor[0];
bMatrix[1][1] = nor[1];
bMatrix[2][1] = nor[2];
bMatrix[1][2] = -nor[2];
if theta > THETA_THRESHOLD_NEGY_CLOSE:
#If nor is far enough from -Y, apply the general case.
bMatrix[0][0] = 1 - nor[0] * nor[0] / theta;
bMatrix[2][2] = 1 - nor[2] * nor[2] / theta;
bMatrix[0][2] = bMatrix[2][0] = -nor[0] * nor[2] / theta;

else:
#If nor is too close to -Y, apply the special case.
theta = nor[0] * nor[0] + nor[2] * nor[2];
bMatrix[0][0] = (nor[0] + nor[2]) * (nor[0] - nor[2]) / -theta;
bMatrix[2][2] = -bMatrix[0][0];
bMatrix[0][2] = bMatrix[2][0] = 2.0 * nor[0] * nor[2] / theta;

else:
#If nor is -Y, simple symmetry by Z axis.
bMatrix = mathutils.Matrix().to_3x3()
bMatrix[0][0] = bMatrix[1][1] = -1.0;

#Make Roll matrix
rMatrix = mathutils.Matrix.Rotation(roll, 3, nor)

#Combine and output result
mat = rMatrix * bMatrix
return mat

def mat3_to_vec_roll(mat):
#this hasn't changed
vec = mat.col[1]
vecmat = vec_roll_to_mat3(mat.col[1], 0)
vecmatinv = vecmat.inverted()
rollmat = vecmatinv * mat
roll = math.atan2(rollmat[0][2], rollmat[2][2])
return vec, roll