# Set a bones matrix to a custom matrix

I have a script in which I compute a custom matrix for each bone of a skeleton. I know I have switch to edit mode but then I read that the property edit_bone.matrix is read-only for bones. Now, because I know how matrices work, I thought, I would just set the bones matrix to Identity() (edit_bone.head to (0, 0, 0) and edit_bone.tail to (0, 1, 0)). But when I then do transform the bone the outcome is not what I expected. The script-snippet is the following:

for bone in self._data.edit_bones:
ebone = armature.edit_bones.new(bone.name)
ebone.tail = (0.0, 1.0, 0.0)
t_matrix = Matrix(bone.transform) # use blender-intern Matrix
# the following call should set ebone.matrix to t_matrix
# cannot do that directly, is currently wrong
ebone.transform(t_matrix)
print(t_matrix, ebone.matrix, sep='\n')


This gives the following output:

<Matrix 4x4 (-0.9665, -0.2564, -0.0119, 17.7405)
( 0.0171, -0.0183, -0.9997,  0.0280)
( 0.2561, -0.9664,  0.0221, -0.0120)
( 0.0000,  0.0000,  0.0000,  1.0000)>
<Matrix 4x4 (-0.0530, -0.0119,  0.9985, 17.7405)
( 0.0227, -0.9997, -0.0107,  0.0280)
( 0.9983,  0.0221,  0.0533, -0.0120)
( 0.0000,  0.0000,  0.0000,  1.0000)>


Notice how the translation is correct but I don't understand why the other part isn't. Can anyone give an insight what is going on and provide a solution to my problem?

• Not sure if it helps, but doesn't the standard bone point in the z-direction? In my oppinion the tail should be set to (0,0,1). – maddin45 May 12 '14 at 11:56
• I checked and no, (0, 1, 0) gives bone.matrix == Matrix.Identity(4) – WorldSEnder May 12 '14 at 11:59
• .matrix can't be set, you need to set .roll and the both endpoints instead. There are 2 utility function: blender.org/documentation/blender_python_api_2_70a_release/… – CodeManX May 12 '14 at 16:32
• Can you please explain a little bit more? Is there a method to calculate the Eigen-vector of a matrix or do I have to write that on my own? Feel free to post a full answer. – WorldSEnder May 12 '14 at 16:37
• I don't know anything about Eigen-vectors, but aren't the available methods sufficient? There's also EditBone.transform() and Object.convert_space(). – CodeManX May 13 '14 at 19:20

bone.transform() also didn't work very well for me, so I used a method I found in a blenderartists post to extract the roll and tail from a matrix:

def vec_roll_to_mat3(vec, roll):
target = Vector((0, 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 = Matrix.Rotation(theta, 3, axis)
else:
updown = 1 if target.dot(nor) > 0 else -1
bMatrix = Matrix.Scale(updown, 3)

# C code:
#bMatrix[0][0]=updown; bMatrix[1][0]=0.0;    bMatrix[2][0]=0.0;
#bMatrix[0][1]=0.0;    bMatrix[1][1]=updown; bMatrix[2][1]=0.0;
#bMatrix[0][2]=0.0;    bMatrix[1][2]=0.0;    bMatrix[2][2]=1.0;
bMatrix[2][2] = 1.0

rMatrix = 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


Then, to set it:

tail, roll = mat3_to_vec_roll(matrix)
bone.roll = roll


The code found in Emd4600's answer works with most, but not all bones. That code was a port of the internal C code of blender, which has meanwhile been improved. I have ported this newer, improved version 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 Emd4600's answer), 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