getting IK constrained rotations

Hi,

I’m trying to grab the bone rotations from an armature for use outside blender. This works fine for a pose with the rotations set explicitly for each bone by using:

bpy.context.active_object.pose.bones[bonename].rotation_quaternion

but if the pose has been set via IK solvers then all I get is the starting position (e.g. Quaternion(1,0,0,0)) not the solved solution that blender finds with the IK. I want to get each bone’s rotation, as I see them on the screen, regardless of what techniques or constraints were used to pose the armature.

Anyone know how to do this?

If you can’t find a way to get the position and orientation of IK transformed bones then one possible option that springs to mind is to write a script that adds keyframes for the bones you are interested in on the frames that you want to capture. This should keyframe the IK deformation and therefore allow you to grab the rotation.

I’ve not tried this. Like I say, it’s just a possible solution.

At one point, I considered writing a script that does just this and thereby ‘bakes’ IK animation into FK animation, but this is yet another one of my many ideas that never got further than a concept. There is possibly a script out there that actually does this…

Thanks Funky, I’m shying away from an implementation that changes the file. After I’ve exported the data I will want to tweak or expand the animation and I’d rather keep the original blender file intact with IK constraints and the lot. I could of course copy the file or armature to a temporary version, but this seem awfully inelegant!

After some testing it seems that the global matrix does track the changes. i.e.

bone = bpy.context.active_object.pose.bones[‘shoulder.L’]

bone.matrix_local will only register direct rotations of the bone (an IK solution will not change the value even though the bone moves on screen)

bone.matrix will change with IK solution. Presumably this is a global coordinate frame.

Here is one potential solution I’m playing around with:

Create empties for each bone I want to extract rotations from and constrain them to copy the target bones location and rotation.
Set up the empties to have the same parent hierarchy as the armature.

So we have say:
eshoulder = bpy.data.objects[‘elshoulder’] # empty tracking bone
lshoulder = bpy.data.objects[“A1”].pose.bones[‘shoulder.L’] #bone of interest

Then in the Rest Position grab the local matrix of the empty and work out its inverse:
ml0i = eshoulder.matrix_local.copy().invert()

Switch back to Pose Position

now, for direct rotations the local matrix of the bone (lshoulder.matrix_local) will be the same as ml0ieshoulder.matrix_local
however ml0i
eshoulder.matrix_local seems to also track the location when it is set by IK.

This kind of makes sense … the hierarchy takes into account the relative positions and orientations and ml0i sets the ‘zero’ position for the bone.

I’ve just compared approximate values for a couple of poses and it seems to work, I haven’t implemented this yet.

Might be possible to do the same trick with lshoulder.matrix (global matrix of bone) which seem related to the
global matrix of the empty (eshoulder.matrix_world) in some rotated and translated way, but then you would have to undo the parent bones transformations by hand I guess.

You have to calculate the difference between the pose.bone.matrix (bone current postition) and the data.bone.matrix_local (bone rest position) or something like that.

Not sure how that works though.

There is a script or two (somewhere) to apply IK bone positions to FK bones (for IK/FK switching) which is basically what you want.

A couple more thoughts…

To use the Empties solution, you could just bone parent them.

I had the issue of undoing parent bone’s rotations recently and found that the complexity can be more easily managed by taking the difference in global rotations between the currently considered bone and it’s parent…

If R_0 is the pose rotation of bone 0, R_1 is the pose rotation of bone 1 etc. and G_0, G_1 etc. are the global rotations compared to rest orientation…

R_0 = G_0
R_1 = G_1 - R_0
R_2 = G_2 - R_1 - R_0
R_3 = G_3 - R_2 - R_1 - R_0

R_(N-1) = G_(N-1) - R_(N-2) - … - R_1 - R_0 (EQUATION 1)
R_N = G_N - R_(N-1) - … - R_1 - R_0 (EQUATION 2)

Substituting (EQUATION 1) into (EQUATION 2):

R_N = G_N - G_(N-1)

(Sorry if this is stating the obvious to you, but thought it might help.)

Agghhhhrrr, so close its getting frustrating.

Uncle [:)] - yes I think you are right but its frustrating as data.bones[‘abdomen’].matrix returns a 3x3 matrix and pose.bones[‘abdomen’].matrix returns a 4x4 matrix that also includes the translations (a full affine transform I’m guessing). I want the latter for both the current and rest positions.

Here is what I have so far without using empties:

  • We can’t use the .matrix_local functions as they do not give the rotations found with IK only the direct rotations
  • Using world matrices .matrix does include the IK motion but the transformations need to be made local by hand

So…

In REST mode (to get default orientation of the coordinate systems):

The current global matrix Mg0 = Mp0*Ml0 where Mp0 is the parents global matrix and Ml0 is the local matrix (the difference relative to the parent). So Ml0 = Mp0^{-1}*Mg0. Now, want inverse of this …

Minv = (Mp0^{-1}*Mg0)^{-1} = Mg0^{-1}*Mp0

Now in POSE mode we can get the relative matrix by

Ml1 = MinvMl1 = MinvMp1^{-1}*Mg1

ok, sorry about the math … in code:

In REST mode:

if bone.parent:
Mp = bone.parent.matrix
else:
Mp = mathutils.Matrix() #identity matrix

Minv = bone.matrix.copy().invert()*Mp.copy()

Then in POSE mode get the local matrix with:

local_matrix = Minv*Mp.copy().invert()*bone.matrix

this local_matrix will be exactly the same as pose.bones[bonename].matrix_local for direct rotations but it will also work to read off IK rotations.

PROBLEM: (Grrrr)

switching from POSE to REST mode works on the console but not in the script!
(I’m using bpy.data.armatures[‘Armature’].pose_position=‘POSE’ etc)
Seems that I need to somehow refresh the data?!?

Alternatively as Uncle points out the Minv can be calculated using data.bones[] instead of pose.bones[] except that would miss the translations. Maybe they can be obtained elsewhere.

FunkyWymn - I don’t think its necessary to go all the way down the chain to the root. The parents global matrix is the result of all the rotations up the chain from the root, so we need to only look at the difference between the current bone and its parent.

Hi aa00,

Very interested in this thread as it is similar to what i’m doing with a change rest position script. On the bvh import scrpt http://blenderartists.org/forum/showthread.php?t=164765&highlight=2.5+sample+scripts it uses

bpy.ops.object.mode_set(mode=‘POSE’) to change between modes.

Seems ops has to be used … similarly with


bpy.data.scenes[1].frame_current = 0
xx = rigObj.pose.bones['Hips'].rotation_quaternion
bpy.data.scenes[1].frame_current = 10
yy =rigObj.pose.bones['Hips'].rotation_quaternion
print(xx - yy)

Will always return (0,0,0,0) despite having different rots at frames 1 and 10… but will change the UI to the frames.

@aa00:

R_N = G_N - G_(N-1)

… The final line of my proof indicates that the difference between global orientation and the parent’s global orientation is the pose rotation required, thus avoiding having to calculate all rotations for all bones in the chain. I presented the proof mathematically since I’ve tried to explain this in words before, but people got confused. It seems that I’ve managed to sow confusion again. :frowning:

I did however forget to mention that this will calculate the rotation in global space and this might need to be transformed into the bone’s local space. It seems you’re on top of this problem though. :slight_smile:

@batFINGER:

from what I’ve seen bpy.ops.object.mode_set(mode=‘POSE’) sets the mode of the 3D view and you can choose between (‘OBJECT’, ‘EDIT’, ‘POSE’). What I’m after is setting the Armature state between ‘POSE’ and ‘REST’, I haven’t found how to do that with the ops module. Changing it via bpy.data.armatures[‘Armature’].pose_position works in the console but there is a delay or some such in a script (I’ve tried sleeping for a few seconds but this doesn’t help.

e.g. try the following in the console:

bone = bpy.context.active_object.pose.bones[‘forearm.L’] # or whatever bone you’ve changed from rest pose

then the following twice:
bpy.data.armatures[‘Armature’].pose_position=‘REST’; bone.matrix

and the following twice:
bpy.data.armatures[‘Armature’].pose_position=‘POSE’; bone.matrix

you will see that the bone matrix is updated only on the second run, not the first. So there is some sort of delay that requires a refresh of the data structures … not sure how to force this.

In your code, *.rotation_quaternion returns the same as *.matrix_local.to_quat() and *.matrix_local will not register any movement that has been obtained as a result of an IK chain … hence this whole thread. *.matrix will register the movement but it is in a global/world coordinate frame and you have to convert it to local by hand.

@FunkyWyrm: ok, I follow. I looked at your explanation and saw a recursive algorithm whereas you were just showing the reasoning for the general case. Yes, I think we are saying the same thing. I’m using matrices you are using rotations. For my application I need the relative rotations to the parent as well as difference from rest and working with matrices seems easier as I can take translations into account too in one hit (e.g. for walk cycles). The maths is fine … I should have read it more carefully :slight_smile:

Oh Ok I misunderstood.

The code i posted also doesn’t work with keyframed actions without IK either… for what that’s worth.

I had a look in console re your post and it worked ok for me but then again I have no IK chains.

I’ve been dabbling with mocap files. Perhaps you could run your script in REST mode save the results to a file… the mocap structure is handy with the node heirachy structure… but anyway whatever … then run it in POSE mode and read the REST mode data from the file and make the appropriate changes. … just a suggestion…

Yes, that was what I was thinking too, run in REST and store the results pickled somewhere (assuming the restpose doesn’t change that often).

What I wrote above with the transformation matrices and the world coordinates seems to be working fine (I have frame 0 with the rest pose as a workaround) - I can pull out the bone’s local orientation relative to the parent and rest mode, in the current frame, as seen on screen including IK chains / other constraints and local rotations. It doesn’t require setting any temporary structures.

This is what I mean about POSE and REST modes needing some sort of update event. I start off in REST mode, then switch to POSE mode and immediately print off the matrix:

>>> active.data.pose_position
'REST'

>>> active.data.pose_position='POSE'; bone.matrix
Matrix((-0.038484, 0.999073, -0.019294, 11.088013), (-0.014974, 0.018730, 0.999712, 40.200428), (0.999147, 0.038762, 0.014240, -1.268387), (0.000000, 0.000000, 0.000000, 1.000000))

>>> active.data.pose_position='POSE'; bone.matrix
Matrix((-0.011348, -0.999563, 0.027340, 8.099199), (0.106351, -0.028394, -0.993924, 45.399223), (0.994265, -0.008370, 0.106628, -3.855516), (0.000000, 0.000000, 0.000000, 1.000000))

>>> active.data.pose_position='REST'; bone.matrix
Matrix((-0.011348, -0.999563, 0.027340, 8.099199), (0.106351, -0.028394, -0.993924, 45.399223), (0.994265, -0.008370, 0.106628, -3.855516), (0.000000, 0.000000, 0.000000, 1.000000))

>>> active.data.pose_position='REST'; bone.matrix
Matrix((-0.038484, 0.999073, -0.019294, 11.088013), (-0.014974, 0.018730, 0.999712, 40.200428), (0.999147, 0.038762, 0.014240, -1.268387), (0.000000, 0.000000, 0.000000, 1.000000))

Notice the matrix only gets updated in the second pass. Whereas if I do separate commands:


>>> active.data.pose_position='POSE'
>>> bone.matrix
Matrix((-0.011348, -0.999563, 0.027340, 8.099199), (0.106351, -0.028394, -0.993924, 45.399223), (0.994265, -0.008370, 0.106628, -3.855516), (0.000000, 0.000000, 0.000000, 1.000000))

>>> active.data.pose_position='REST'
>>> bone.matrix
Matrix((-0.038484, 0.999073, -0.019294, 11.088013), (-0.014974, 0.018730, 0.999712, 40.200428), (0.999147, 0.038762, 0.014240, -1.268387), (0.000000, 0.000000, 0.000000, 1.000000))

presumably some sort of update happens in between the commands while on the console. Maybe this only occurs in linux?

That update ‘bug’ is a limitation of the event system – the updates don’t get run until after the script is finished or something like that. Couldn’t hurt to report it to the bug tracker but that’s probably what they’ll say also.

On the bone position thing…

slikdigit was saying (on the IRC) that there’s functions in the copy attributes addon that shows how to get/set bone locations to/from local/global space so you can apply the position of a bone that’s part of an IK chain for eg. Apply visual location or some such.

Hey Uncle- thank you, that solved it. Please pass on my thanks to slikdigit too if you get the chance.

Here is the function I now have that will return a bones visual transformation:


def visualmatrix(armature, bone_name):
    '''
        return a local transformation matrix that 
        captures the visual transformation including
        IK chain etc
    '''
    pose_bone = armature.pose.bones[bone_name]
    data_bone = armature.data.bones[bone_name]

    M_pose = pose_bone.matrix
    M_data = data_bone.matrix_local

    # grab the parent's world pose and rest matrices
    if data_bone.parent:
        M_parent_data = data_bone.parent.matrix_local.copy()
        M_parent_pose = pose_bone.parent.matrix.copy()
    else:
        M_parent_data = mathutils.Matrix()
        M_parent_pose = mathutils.Matrix()

    visual_matrix = M_data.copy().invert() * M_parent_data * M_parent_pose.invert() * M_pose

    return visual_matrix

Hi @aa00,

wow, this saved me. Great, and this works still in blender 2.8.x with my silly changes:

def visualmatrix(armature, bone_name):
    '''
        return a local transformation matrix that 
        captures the visual transformation including
        IK chain etc
    '''
    pose_bone = armature.pose.bones[bone_name]
    data_bone = armature.data.bones[bone_name]
    
    M_pose = pose_bone.matrix
    M_data = data_bone.matrix_local

    # grab the parent's world pose and rest matrices
    if data_bone.parent:
        M_parent_data = data_bone.parent.matrix_local.copy()
        M_parent_pose = pose_bone.parent.matrix.copy()
    else:
        M_parent_data = mathutils.Matrix()
        M_parent_pose = mathutils.Matrix()

    M1 = M_data.copy()
    M1.invert()

    M2 = M_parent_pose.copy()
    M2.invert()

    visual_matrix = M1 @ M_parent_data @ M2 @ M_pose

    return visual_matrix
1 Like