3
$\begingroup$

I'm simply trying to find the global rotation of a bone in pose mode using python

i.e. what angle a bone makes with the global axis?

enter image description here

The closest I've come is:

for pb in context.selected_pose_bones_from_active_object:
    
    
    pb_world_matrix=armature.matrix_world @ pb.matrix
    loc, rot, scale = pb_world_matrix.decompose()
    rot= rot.to_euler();
    
    print(pb.name,"x=",math.degrees(rot.x),"y=",math.degrees(rot.y),"z=",math.degrees(rot.z))

Edit:
let me explain what I mean with an example:
enter image description here

As you can see in the gif above, initially the angles are:

x_glob= 0deg
y_glob= 0deg
z_glob= 0deg

then I first rotate it by 38deg on global y orientation then I rotate by 41.3deg on global x orientation so the final output should be:

x_glob= 41.3deg
y_glob= 38deg
z_glob= 0deg

I think a better way of looking at the problem is finding the angle made with the planes of global axis (I couldn't find the wording to phrase it till now)

$\endgroup$
2
  • $\begingroup$ In your example you don't show any parenting, so bone's local rotation is also global rotation... Except Y and Z are swapped. $\endgroup$ Jun 20 at 13:56
  • $\begingroup$ @MarkusvonBroady I didn't want to make the question too complex & confusing so I didn't add that part, but when I meant globally I meant the rotation regardless of parenting $\endgroup$
    – cakelover
    Jun 20 at 14:40

3 Answers 3

2
$\begingroup$

I had a little look into it but I'm not an expert. Discovered you can get the matrix world of the selected bone using pb_mw = pb.matrix. Then using pb_mw.to_euler() you can obtain the euler rotation from the matrix. The only catch seemed to be that a bone at rest position in the interface (x,y,z rotation at 0 degrees in the UI) is actually at 90 degrees on the x axis in the world matrix. Happy for further input on this.

import bpy
from math import degrees

context = bpy.context

for pb in context.selected_pose_bones_from_active_object:
    
    pb_mw = pb.matrix
    pb_rot = pb_mw.to_euler()
    
    print(f"pb matrix world: {pb_mw}")
    print(f"x rotation: {degrees(pb_rot.x) - 90}")
    print(f"y rotation: {degrees(pb_rot.y)}")
    print(f"z rotation: {degrees(pb_rot.z)}")

# Test on empty  
# empty = bpy.data.objects["Empty"]
# empty.matrix_world = pb_mw

Edit: related answer here... Get bone rotation in pose mode using Python

$\endgroup$
8
  • $\begingroup$ I don't see how this gives you the global rotation? $\endgroup$
    – cakelover
    Jun 20 at 11:33
  • $\begingroup$ The pb.matrix is the matrix world of the bone which has the global rotation in it... you simply use .to_euler() to extract just the rotation part of it. $\endgroup$
    – Dan
    Jun 20 at 11:34
  • $\begingroup$ that does not give me the global rotation i.e. what angle a bone makes with the global axis? $\endgroup$
    – cakelover
    Jun 20 at 12:04
  • $\begingroup$ @cakelover you might need give some more details about what angle you are trying to calculate exactly, because the matrix world does give the global rotation as far as I can see? $\endgroup$
    – Dan
    Jun 20 at 12:21
  • $\begingroup$ I've added an example, does that clear it up? $\endgroup$
    – cakelover
    Jun 20 at 12:57
2
$\begingroup$

You can't do this.

Why? It is because you have converted rotation from quaternions to Euler angles. That's basically means, that you have dozens of variants to have the same rotation using Eulers. Let me show the example:

Here are 2 figures rotated same way, but, first one has rotation 90,90,0:

enter image description here

second 180, 90, 90

enter image description here

As you see, you can't get initial transforms of the object.

Using rot.to_euler(); gives you one of the variants. But you may get something different, that you have expected.

Of course, you have some initial rotation values, but it's hard to convert them to global without using a transform matrix. And when you put values in the matrix, there is no way to return them back unchanged.

$\endgroup$
7
  • $\begingroup$ I had a feeling that might be the case, that's why I modified the question and added planes, finding the rotation with respect to planes would achieve what I'm looking for, sorry for phrasing the question poorly initially $\endgroup$
    – cakelover
    Jun 20 at 13:48
  • $\begingroup$ It's not really hard, you need to iterate over parents and rotate by negated Euler rotation components in reversed order. $\endgroup$ Jun 20 at 13:55
  • $\begingroup$ @MarkusvonBroady I'm not sure, you have missed possible scaling. $\endgroup$
    – Crantisz
    Jun 20 at 14:13
  • $\begingroup$ @Crantisz scaling won't affect rotation, unless of course I don't understand the OP. $\endgroup$ Jun 20 at 14:14
  • $\begingroup$ Uniform maybe yes, but not uniform affects $\endgroup$
    – Crantisz
    Jun 20 at 14:36
0
$\begingroup$

I think I've found a solution

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

unit_x=Vector((1.0,0.0,0.0))
unit_y=Vector((0.0,1.0,0.0))
unit_z=Vector((0.0,0.0,1.0))

for pb in context.selected_pose_bones_from_active_object:
    
    axis, roll = pb.bone.AxisRollFromMatrix(pb.matrix.to_3x3(), axis=pb.y_axis)

    x_angle=Vector((pb.vector.x,0,pb.vector.z)).angle(unit_x) #angle made with x plane
    y_angle=Vector((0,pb.vector.y,pb.vector.z)).angle(unit_y) #angle made with y plane
    z_angle=Vector((pb.vector.x,0,pb.vector.z)).angle(unit_z) #angle made with z plane
    
    print("angle with plane x:",math.degrees(x_angle))
    print("angle with plane y:",math.degrees(y_angle))
    print("angle with plane z:",math.degrees(z_angle))
    print("self roll:",math.degrees(roll))

added a little roll bonus in there

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.