# PoseBone local rotation values to global with axis changed

I think this question has been ask a few times but haven't been able to produce an answer.

So for an addon I'm building I need to send the bone rotations of an armature. Those rotations are expected to be in global coordinates. So after doing to bone rotations that I need, I collect the rotations like so:

        for bone in context.object.pose.bones:

bone_idx: int = get_bone_index(skeleton_info, bone)

idx = list(context.object.pose.bones).index(bone)
m = matrices[idx]
axis_angle = bone.rotation_quaternion.to_axis_angle()
v_axis = axis_angle[0]
result = (bone.id_data.matrix_world.to_3x3().inverted()
@ m.inverted()
@ v_axis)

result.normalize()

result = result * axis_angle[1]
pose_np[bone_idx*3] = result[0]
pose_np[bone_idx*3+1] = result[1]
pose_np[bone_idx*3+2] = result[2]


As you can see I get the quaternion local value, get the axis angle and perform some transformations on the vector representing that axis in order to get to the system of coordinates I want.

The thing is the matrices I expect contain weird values. I don't really understand how to use the matrices inside the PoseBone class to achieve what I want to do... The usage of matrix, matrix_local and matrix_channel confuses me especially to see how each bone accumulates transformations and whether if I need to call the inverse of the parent's object matrix world... Could you please give me some hints on what I might be doing wrong?

Thanks!

Not sure what you need. Try this for rotation between local space and world space

for pbone in context.object.pose.bones:
# This is the transform from bone local space to world space
world = context.object.matrix_world @ pbone.matrix

# If there are non-uniform scalings or shears in world, this won't work...
_loc, rot, _scale = world.decompose()

print(rot.to_axis_angle())


If you want the relative rotation from the rest pose to the current pose in world space, maybe this

for pbone in context.object.pose.bones:
world = context.object.matrix_world @ pbone.matrix
world_rest = context.object.matrix_world @ pbone.bone.matrix_local

_loc, rot, _scale = world.decompose()
_loc, rot_rest, _scale = world_rest.decompose()

print(rot.rotation_difference(rot_rest).to_axis_angle())


I ended up doing the following:

        for pose_bone in context.object.pose.bones:

# We'll translate from bone coords to avatar coords
# using the matrix_local matrix in the actual bone
# (not the pose bone) which contains the rest pose matrix

# From matrix_local we'll extract the rotation
rot = pose_bone.bone.matrix_local.decompose()[1]
rot = rot.to_matrix().to_3x3()

# Apply the rotation to the direction axis angle
axis_angle = pose_bone.rotation_quaternion.to_axis_angle()
new_axis = rot @ axis_angle[0]
new_axis.normalize()

# Get the actual axis angle multiplying the magnitude
# with the unitary vector calculated earlier
result = new_axis * axis_angle[1]

# Assign the rotations to the locations avatar-core expects
# on the pose numpy array
bone_idx: int = get_bone_index(skeleton_info, pose_bone)
pose_np[bone_idx*3] = result[0]
pose_np[bone_idx*3+1] = result[1]
pose_np[bone_idx*3+2] = result[2]


Object.convert_space

There is a convenience method available on objects to help convert spaces. For pose bones, can pass the pose bone, its pose matrix pb.matrix and convert it from 'POSE' space to world.

Little example script, to run in 'OBJECT' mode, only because it is placing empties using calculated world matrix. The add empty operator requires 'OBJECT' mode.

import bpy
context = bpy.context
ob = context.object

for pb in ob.pose.bones:
mw = ob.convert_space(
pose_bone=pb,
matrix=pb.matrix,
from_space='POSE',
to_space='WORLD',
)

mt = context.object
mt.empty_display_type = 'ARROWS'
mt.matrix_world = mw


Note this aligns empties with blender bones, such the shaft of the bone is in the Y axis of the world matrix.

Or from a bones local space with

mw = ob.convert_space(
pose_bone=pb,
matrix=pb.matrix_basis,
from_space='LOCAL',
to_space='WORLD',
)


Should you require to change this to can use bpy_extras.io_utils.axis_conversion to transform to another space

>>> bpy_extras.io_utils.axis_conversion(
axis_conversion(from_forward='Y', from_up='Z', to_forward='Y', to_up='Z')
Each argument us an axis in ['X', 'Y', 'Z', '-X', '-Y', '-Z']
where the first 2 are a source and the second 2 are the target.


Same as above with resultant world matrix pointing z axis along bone.

import bpy
context = bpy.context
ob = context.object
from bpy_extras.io_utils import axis_conversion

A = axis_conversion(
to_up='Y',
to_forward='Z'
).to_4x4()

for pb in ob.pose.bones:
mw = ob.convert_space(
pose_bone=pb,
matrix=pb.matrix,
from_space='POSE',
to_space='WORLD',
)