0
\$\begingroup\$

I need this for a Blender3d script, but you don't really need to know Blender to answer this.

I need to get bone head and tip positions from a transform matrix read from a file. The position of base is the location part of the matrix, length of the bone (distance from base to tip) is the scale, position of the tip is calculated from the scale (distance from bone base) and rotation part of the matrix.

So how to calculate these?

bone.base([x,y,z]) # x,y,z - floats
bone.tip([x,y,z])
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

You can find the base by multiplying the origin vector (0, 0, 0) by the transform matrix.

The tip question is slightly more in-depth: it really depends on what the canonical direction is that all the bones are defined from - in other words, where does the bone originally rotate from?

One way or another, to get the tip position you multiply a unit vector (for example, in Z this would be (0, 0, 1)) by the transformation matrix.

Bear in mind as well that your bones are probably hierarchical and the position will be relative to its parent joint - you will need to multiply one particular joint's matrix by its parent (and the parent joint by its parent, and so on) to get a global, world position.

\$\endgroup\$
9
  • \$\begingroup\$ No worries about hierarchy - isn't any. So what about the tip position? I think for the lenght you assumed bone's original rotation is facing the z-axis. Let's assume the same when calculating tip position. Now what is the actual formula? \$\endgroup\$
    – ddos
    Jul 10, 2012 at 5:33
  • \$\begingroup\$ I guess I don't need to get the size because by setting tip and base position that is set automatically. \$\endgroup\$
    – ddos
    Jul 10, 2012 at 5:44
  • 1
    \$\begingroup\$ That should be the answer. If the scale denotes the length, and rotation the direction, then multiplying a unit vector (Z if all bones originally faced Z, X if X, and so on) then the resulting position will be the tip's position. I'll modify the answer to correct that. \$\endgroup\$
    – Matt Kemp
    Jul 10, 2012 at 8:10
  • \$\begingroup\$ I just tried this with Blender 2.62 and it doesn't seem to work. I mean it works, the math is correct, but when inspecting a skeleton imported with my importer for Blender 2.4 and then Blender 2.6, you can see the bone roll (rotation relative to itself) is different. The same happens if you do bone.tail = mathutils.Vector((0,1,0)) and then bone.transform(mymatrix). \$\endgroup\$
    – user17402
    Jul 10, 2012 at 16:11
  • \$\begingroup\$ I don't have Blender to hand, but how does it store the initial roll of the bone? If I understand you correctly the bone tip is in the correct place but the orientation is wrong. How does it store this orientation? If it's a normal vector, the solution is simple: multiply the normal by the matrix too (with the W component as zero, so (nX, nY, nZ, 0)). This will apply the rotation (and scale, so you'll probably have to re-normalize) without applying the translation. \$\endgroup\$
    – Matt Kemp
    Jul 10, 2012 at 20:13
0
\$\begingroup\$

If you want to preserve the original transform so that Blender's bones recreate the same transform, you will also need to calculate the bone roll.

Blender has a function to calculate the bone head, tail and roll from a transform matrix in C code. I haven't found a convenient way to access this functionality from the Python API, but fortunately the functions are short and can easily be translated into Python.

Below is a direct translation of the functions you need, for Blender 2.62 or later:

def vec_roll_to_mat3(vec, roll):
    target = Vector((0,1,0))
    nor = vec.normalized()
    axis = target.cross(nor)
    if axis.dot(axis) > 0.000001:
        axis.normalize()
        theta = target.angle(nor)
        bMatrix = Matrix.Rotation(theta, 3, axis)
    else:
        updown = 1 if target.dot(nor) > 0 else -1
        bMatrix = Matrix.Scale(updown, 3)
    rMatrix = Matrix.Rotation(roll, 3, nor)
    mat = rMatrix * bMatrix
    return mat

def mat3_to_vec_roll(mat):
    vec = mat.col[1]
    vecmat = vec_roll_to_mat3(mat.col[1], 0)
    vecmatinv = vecmat.inverted()
    rollmat = vecmatinv * mat
    roll = math.atan2(rollmat[0][2], rollmat[2][2])
    return vec, roll

pos = transform.to_translation()
axis, roll = mat3_to_vec_roll(transform.to_3x3())

bone.head = pos
bone.tail = pos + axis
bone.roll = roll
\$\endgroup\$
10
  • \$\begingroup\$ Shame it's not part of the Python API. Weird as I didn't need to do this in 2.4. Anyway, will try this right away. Thanks! \$\endgroup\$
    – user17402
    Jul 10, 2012 at 21:04
  • \$\begingroup\$ OK, it works 99%. My characters left hand palm is facing the opposite direction as well as two fingers on the right hand. Weird that only few bones are like this. BTW, do I need to do something similar for animation matrices? I'm having problem with some PoseBone deformations. \$\endgroup\$
    – user17402
    Jul 10, 2012 at 21:11
  • 1
    \$\begingroup\$ For the pose bones you can calculate and set the matrix_basis directly from your animation and rest pose matrices. \$\endgroup\$
    – ccxvii
    Jul 10, 2012 at 21:18
  • \$\begingroup\$ Yeah OK. I'm also having trouble settings poseMatrices when bones are parented. But that's a separate question I'll post tomorrow. Thanks for your help. BTW, I fixed those few bones by increasing the floating point precision here: if axis.dot(axis) > 0.0000000001 \$\endgroup\$
    – user17402
    Jul 10, 2012 at 21:21
  • \$\begingroup\$ OK, I was wrong! That didn't solve it, still have problems (wrong angles in-game) with some bones. Help. BTW, please link me to the original C code if you can, I always get lost in program sourcecodes. \$\endgroup\$
    – user17402
    Jul 11, 2012 at 7:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .