# Background

I have a number of bones defined as positions + rotation quaternions.

In blender I understand bones are defined as a head, a tail and a roll. With the head -> tail vector being the localY axis

Its easy to get the head (position) and the tail (position + localY * arbitraryDistance) but it’s harder to get the roll. The roll is obviously around the localY axis, but beyond that I’m not sure how it’s defined. I have been finding the localZ from the quaternion and then fiddling with the roll until it matches but that has been painful, and doesn’t suit itself to the programmatic workflow I want.

# Question

How can I get the blender bone roll from a Quaternion? (Or from a Local Z axis, which I can also calculate from the Quaternion)

• I'm confused as to whether this question is for edit bones or pose bones. Which one do you mean? Oct 8, 2023 at 8:05
• @tetii edit bones (context is that I have bone positions provided by a vr library and I want to build a model around those bones; I never pose in blender itself) Oct 8, 2023 at 16:38
• OKay, I found it strange that Quarternion appeared instead of EditBone.matrix. Oct 9, 2023 at 4:47

# Calculate a edit_bone's roll using a quaternion

## Concept

I believe that the following relationship holds.

$$M=M_{\phi} M_{roll}$$
where

• $$M$$ is the matrix of an edit_bone.matrix.to_3x3(),
• $$M_{roll}$$ is a roll matrix and,
• $$M_{\phi}$$ is a matrix to match a vector(0,1,0) with the edit_bone.vector direction.

Therefore
$$M_{roll} = M_{\phi}^{-1} M$$

## Script

import bpy
from mathutils import Matrix, Vector
from math import *

def calc_roll_from_quaternion(quaternion):

mat = quaternion.to_matrix()    # corresponds to EditBone.matrix.to_3x3()
vec_yo = Vector([0,1,0])
vec = mat @ vec_yo              # corresponds to EditBone.vector

# Get the rotation matrix to match vec_yo with vec direction
if vec.cross(vec_yo).length == 0 and vec.dot(vec_yo) < 0:
# Correct the rotation matrix when the direction of vec is equal to -vec_yo
rot = Matrix.Diagonal((-1,-1,1),)
else:
diff_angle = vec_yo.rotation_difference(vec)
rot = vec_yo.rotation_difference(vec).to_matrix()

mat_roll = rot.inverted() @ mat
v, angle = mat_roll.to_quaternion().to_axis_angle()

from numpy import sign
angle *= sign(v.y)

return angle

bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object

print(obj.name)

for ebone in obj.data.edit_bones:

quaternion = ebone.matrix.to_3x3().to_quaternion()
calc_roll = calc_roll_from_quaternion(quaternion)

if abs(calc_roll - ebone.roll) > 1e-4:
diff = calc_roll - ebone.roll
print(f"error of {ebone.name}:   {degrees(diff):.4f} deg")


## Results of testing with Human (Meta-Rig)

metarig.001
error of cheek.B.L:   -0.0068 deg
error of cheek.B.R:   0.0068 deg
error of shoulder.L:   -0.0229 deg
error of shoulder.R:   0.0229 deg

• Your function does seem to be consistent. There seems to be something different between the bones quaternion and the quaternion I'm using to get the localY and localZ but I'm not sure what that difference is (I realise that is annoying). So I think this is the right answer, but seems not to help me Oct 8, 2023 at 20:14
• @Richard Tingle, I thought I answered your question, "but it's harder to get the roll". It sounded like you were saying that if that could be resolved, the second half of the problem could be resolved. Oct 8, 2023 at 22:18
• @tetil I think I know what the problem is. I think one is in a +y up coordinate system and the other in blenders coordinate system. I'll have annother play tomorrow, converting between the two may sort it out Oct 8, 2023 at 22:50
• Yes, now that I've sorted out my own quaternion issues your roll calculator works perfectly. Thanks! Oct 9, 2023 at 16:55