1
$\begingroup$

I am having some problems with blender bones.

In short this is my issue:

  1. In python I am creating some bones.
  2. I transform them in edit mode using a specific matrix.
  3. Now I need to read back the mentioned matrix. How can I do that?

Check the script to clarify may be:

import bmesh
import bpy
import mathutils

# create skin container
asset_arm_name = '' + '_sk'
bpy.ops.object.armature_add()
narmature = bpy.context.scene.objects.active
narmature.name = asset_arm_name
arm = narmature.data
arm.name = asset_arm_name

# enter edit mode to start creating bones
arm_obj = bpy.data.objects[asset_arm_name]
bpy.context.scene.objects.active = None
bpy.context.scene.objects.active = arm_obj

bpy.ops.object.mode_set(mode='EDIT', toggle=False)
edit_bones = arm_obj.data.edit_bones
arm.edit_bones.remove(arm.edit_bones[0]) #remove default bone    

# calc transformations
x = [[2.220446049250313e-16, -1.0, 0.0], [1.0, 2.220446049250313e-16, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 0.0]]
tr=mathutils.Matrix([i+[0] for i in x])
tr[3][3]=1.0

bone = edit_bones.new('bone')
bone.head=[0,0,0]
bone.tail=[0,0,10]
bone.transform(tr)

print('=====================')
print(tr)
print(arm_obj.data.bones[0].matrix)
x=arm_obj.data.bones[0].matrix
x.transpose()
print(x)

So, given a bone at 0,0,0 with no previous transformations... if I transformed a bone using a tr_matrix. How can I get the transformation matrix that I just applied using the blender api? In the previous example this is the tr_matrix applied to the bone:

tr_matrix = <Matrix 4x4 (0.0000, -1.0000, 0.0000, 0.0000)
        (1.0000,  0.0000, 0.0000, 0.0000)
        (0.0000,  0.0000, 1.0000, 0.0000)
        (0.0000,  0.0000, 0.0000, 1.0000)>

And this is what I am getting from the bone:

arm_obj.data.bones[0].matrix = <Matrix 3x3 (1.0000, 0.0000,  0.0000)
        (0.0000, 0.0000, -1.0000)
        (0.0000, 1.0000,  0.0000)>

Multiplying by parent transform, transposing or converting to_4x4 doesn't help. I tried through arm_obj.data.bones and arm_obj.pose.bones, using matrix or matrix_local... but I don't manage to get tr_matrix again, and I have been searching for some days already.

May be I am missing something?

Note: In case you are missing the use case, I am evaluating blender to use it in our pipeline. We have a custom mesh format. Importing is fine, all bones, skin, etc.. all ends in the correct places. But exporting that same data without any changes is giving me different results. The problem is in the bones. I don't manage to get their world transform. I can set it (using bone.transform(tr_matrix)) but when asking for the transformation I am getting different data. So, I assume I am doing something wrong, and this example is probably simple enough to find out what. I hope!

$\endgroup$
  • $\begingroup$ Pretty much your transform matrix rotates 90 degrees about Z. no scale no translation. It is applied to the bone. Only the result "matters" to the bone matrix, it doesn't store the transform. In this case above could recompose the transform from the rotational difference or rotation component Euler((0, 0, pi / 2)).to_matrix() $\endgroup$ – batFINGER Feb 16 at 3:23
  • $\begingroup$ well yeah @batFINGER the example is pretty simple. Its simple in order to focus on getting the transformation from blender, and the transformation is simple enough to check if we got it right. A bone should store the transformations applied to it, current rotation, position etc.. I am just asking how to get that from blender in this particular example. I also provided the code thinking that would make it more clear to understand. The question may be even more simple "Whats the relationship between: bone.transform(matrix) and the data stored for the bone in arm_obj.data, arm_obj.pose, etc...?" $\endgroup$ – user2854611 Feb 17 at 19:10
0
$\begingroup$

Edit bone transforms are defined with head, tail and roll

Edit bones store the data in head, tail and roll. Applying the transform matrix as above to the edit bone sets the roll to 90 degrees. It does not translate the head or tail. Your choice of bone head and tail align to the z axis and the transform matrix is rotating around the z axis 90 degrees.

Note: To get the adjusted value of bone matrix need to toggle back into object mode

bone.transform(tr)
bpy.ops.object.mode_set() # back to object mode
print('=====================')

Running the code above without the transform M1 and with M2 and getting the bone matrix after toggling back into object mode. , calculate the transform from M1 to M2

>>> M1 = Matrix([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
>>> M2 = Matrix([[0, 0, 1], [1, 0, 0], [0, 1, 0]])
>>> T = M2 * M1.inverted()
>>> T
Matrix(((0.0, -1.0, 0.0),
        (1.0, 0.0, 0.0),
        (0.0, 0.0, 1.0)))

>>> T.to_euler()
Euler((0.0, -0.0, 1.5707963705062866), 'XYZ')

Remember that part of this matrix is the arbitrary choice of z axis for bone direction. In bone space the y axis is along the direction of the bone. Notice how both bone matrices above will transform Y axis to Z.

>>> y = Vector((0, 1, 0))
>>> M1 * y
Vector((0.0, 0.0, 1.0))

>>> M2 * y
Vector((0.0, 0.0, 1.0))

To sum up, to calculate what transform has been applied to get from A to B, need A. A is not simply a "zero" in this case since you have defined head and tail before the transform.

If you make the bone tail above scalar * Vector((0, 1, 0)) ie Y axis direction, the bone matrix will be identity

Edit re comment

You don't get the "wrong" rotation you get an equivalent rotation. If you use the resulting bone matrix from above as your transform matrix, you will get same result for both. (the left hand rule.. or right can't remember) This goes back to my first comment under question.

Eg you can rotate default cone to make it point from up to forward in a number of ways. -90 around x axis or 90 around y 90 around z, different transforms same result. In object mode we can see the transform. In edit mode, just like with the edit bone, only the result is saved, what transforms were used to create that result, even given the starting point this is unknown.

Blender creates the bone matrix based on head and tail and roll. Trying to match back the transform used from the result isn't 1:1

Let's investigate the two matrices from latest example

>>> tr
Matrix(((0.0, 1.0, 0.0, 10.0),
        (1.0, 0.0, 0.0, 21.0),
        (0.0, 0.0, -1.0, 0.0),
        (-0.0, 0.0, -0.0, 1.0)))

>>> tr2
Matrix(((0.0, 1.0, 0.0, 10.0),
        (-1.0, 0.0, 0.0, 21.0),
        (0.0, 0.0, 1.0, 0.0),
        (-0.0, 0.0, -0.0, 1.0)))

>>> q = tr.to_quaternion()
>>> q2 = tr2.to_quaternion()
>>> q.rotation_difference(q2).to_euler()
Euler((-0.0, -3.1415927410125732, -0.0), 'XYZ')

Note the difference in rotation between the two is a full revolution in one axis,

Using the resultant bone matrix instead as transform matrix.

===============
<Matrix 4x4 ( 0.0000, 1.0000,  0.0000, 10.0000)
            (-1.0000, 0.0000,  0.0000, 21.0000)
            ( 0.0000, 0.0000,  1.0000,  0.0000)
            (-0.0000, 0.0000, -0.0000,  1.0000)>
<Matrix 4x4 ( 0.0000, 1.0000, 0.0000, 10.0000)
            (-1.0000, 0.0000, 0.0000, 21.0000)
            ( 0.0000, 0.0000, 1.0000,  0.0000)
            ( 0.0000, 0.0000, 0.0000,  1.0000)>

How do you export bones relative to parent?

Exporting trouble. Bone tail and head positions change quaternion rotation

$\endgroup$
  • $\begingroup$ Thanks @batFINGER for the answer. For what you are saying, looks like its not possible to create a bone without a transformation applied (just leaving head and tail empty will not create a bone with the identity transform matrix as I would expect) . Another question pops up now then, do I have to set the tail/head or there is a way to create a bone with "zero" transformation? $\endgroup$ – user2854611 Feb 18 at 12:34
  • $\begingroup$ If you make the bone tail (0, 1, 0) ie Y axis, the bone matrix will be identity. $\endgroup$ – batFINGER Feb 18 at 13:37
  • $\begingroup$ I tried with a more complex transformation (changed bone tail 0,1,0). If I use the upper matrix to transform I get the one below with wrong rotation: ` <Matrix 4x4 ( 0.0000, 1.0000, 0.0000, 10.0000) ( 1.0000, 0.0000, 0.0000, 21.0000) ( 0.0000, 0.0000, -1.0000, 0.0000) (-0.0000, 0.0000, -0.0000, 1.0000)> <Matrix 4x4 ( 0.0000, 1.0000, 0.0000, 10.0000) (-1.0000, 0.0000, 0.0000, 21.0000) ( 0.0000, 0.0000, 1.0000, 0.0000) ( 0.0000, 0.0000, 0.0000, 1.0000)> ` check code here codepad.co/snippet/9YLEywfq $\endgroup$ – user2854611 Feb 18 at 22:06
  • $\begingroup$ You don't get the "wrong" rotation you get an equivalent rotation. If you use the resulting bone matrix from above as your transform matrix, you will get same result for both. (the left hand rule.. or right can't remember) This goes back to my first comment under question. Eg you can rotate default cone to make it point from up to forward in a number of ways. -90 around x axis or 90 around y 90 around z, different transforms same result. Trying to match the transform used from the result isn't 1:1. $\endgroup$ – batFINGER Feb 19 at 2:54
  • $\begingroup$ I feel all I can do here is try and explain the results of the code as shown in question. Some explanation of the source of the transform matrices, the pertinent import code, the result expected will shed more light on this. Using the y axis was only a suggestion re it being the "zero" (identity bone matrix) state of a blender edit bone. What is the local bone axis set up of source format etc? $\endgroup$ – batFINGER Feb 19 at 10:00

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.