0
\$\begingroup\$

Read this forum topic for more info: http://blenderartists.org/forum/showthread.php?260602-transform-matrix-to-bone-%28head-tail-roll%29-bug

I'm trying to get my Blender3d modeller importer to create correct bones from my file's transform matrices.

The Blender Python API doesn't have a way to do this (anymore!), so people had to dig the Blender C source to find out how Blender does it and port that. Here's a Python port of the needed functions, not by me (I don't know C):

def vec_roll_to_mat3(vec, roll):
    target = mathutils.Vector((0,1,0))
    nor = vec.normalized()
    axis = target.cross(nor)
    if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix
        axis.normalize()
        theta = target.angle(nor)
        bMatrix = mathutils.Matrix.Rotation(theta, 3, axis)
    else:
        updown = 1 if target.dot(nor) > 0 else -1
        bMatrix = mathutils.Matrix.Scale(updown, 3)
    rMatrix = mathutils.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

How to use them:

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

bone = armature.edit_bones.new('name')
bone.head = pos
bone.tail = pos + axis
bone.roll = roll

Sadly, it seems even the Blender C code is buggy and doesn't take into account cases when the bone is parallel to (0,1,0). So such bones can get assigned wrong (by 180 degrees) rolls and mess up animations.

Does anyone have better code for generating roll which takes into account such cases?

The old Blender 2.4 API generated correct ones. Its C code can be found here: http://svn.blender.org/svnroot/bf-blender/branches/blender-2.47/source/blender/blenkernel/intern/armature.c I'm no C coder myself, again.

\$\endgroup\$
  • \$\begingroup\$ So you're asking that someone who knows C and Python to convert the C code to Python? \$\endgroup\$ – MichaelHouse Jul 18 '12 at 14:45
  • 1
    \$\begingroup\$ No, that would be one of the few ways to answer this (but only if the C functions in 2.4 are different from the C functions in 2.6 which were ported, which seems to be so since my Blender 2.4 importer works fine). Other ways to answer would be to explain in words what needs to be done, or put together some pseudocde, or modify the existing Pythin functions to work for this scenario. \$\endgroup\$ – user17402 Jul 18 '12 at 16:59
1
\$\begingroup\$

For anyone looking for a solution to this problem, I'm posting this wherever I found it referred to. I have ported this newer upated internal C code of blender into python. I have not extensively tested it, but it seems correct, produces good results and it was super easy to port. It is used in the exact same way as before (see the above question), should be more accurate and get all (?) bone orientations right!

def vec_roll_to_mat3(vec, roll):
    #port of the updated C function from armature.c
    #https://developer.blender.org/T39470
    #note that C accesses columns first, so all matrix indices are swapped compared to the C version

    nor = vec.normalized()
    THETA_THRESHOLD_NEGY = 1.0e-9
    THETA_THRESHOLD_NEGY_CLOSE = 1.0e-5

    #create a 3x3 matrix
    bMatrix = mathutils.Matrix().to_3x3()

    theta = 1.0 + nor[1];

    if (theta > THETA_THRESHOLD_NEGY_CLOSE) or ((nor[0] or nor[2]) and theta > THETA_THRESHOLD_NEGY):

        bMatrix[1][0] = -nor[0];
        bMatrix[0][1] = nor[0];
        bMatrix[1][1] = nor[1];
        bMatrix[2][1] = nor[2];
        bMatrix[1][2] = -nor[2];
        if theta > THETA_THRESHOLD_NEGY_CLOSE:
            #If nor is far enough from -Y, apply the general case.
            bMatrix[0][0] = 1 - nor[0] * nor[0] / theta;
            bMatrix[2][2] = 1 - nor[2] * nor[2] / theta;
            bMatrix[0][2] = bMatrix[2][0] = -nor[0] * nor[2] / theta;

        else:
            #If nor is too close to -Y, apply the special case.
            theta = nor[0] * nor[0] + nor[2] * nor[2];
            bMatrix[0][0] = (nor[0] + nor[2]) * (nor[0] - nor[2]) / -theta;
            bMatrix[2][2] = -bMatrix[0][0];
            bMatrix[0][2] = bMatrix[2][0] = 2.0 * nor[0] * nor[2] / theta;

    else:
        #If nor is -Y, simple symmetry by Z axis.
        bMatrix = mathutils.Matrix().to_3x3()
        bMatrix[0][0] = bMatrix[1][1] = -1.0;

    #Make Roll matrix
    rMatrix = mathutils.Matrix.Rotation(roll, 3, nor)

    #Combine and output result
    mat = rMatrix * bMatrix
    return mat

def mat3_to_vec_roll(mat):
    #this hasn't changed
    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
\$\endgroup\$

Your Answer

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