2
$\begingroup$

I have a script in which I compute a custom matrix for each bone of a skeleton. I know I have switch to edit mode but then I read that the property edit_bone.matrix is read-only for bones. Now, because I know how matrices work, I thought, I would just set the bones matrix to Identity() (edit_bone.head to (0, 0, 0) and edit_bone.tail to (0, 1, 0)). But when I then do transform the bone the outcome is not what I expected. The script-snippet is the following:

for bone in self._data.edit_bones:
    ebone = armature.edit_bones.new(bone.name)
    # ebone.head = (0.0, 0.0, 0.0) # already default
    ebone.tail = (0.0, 1.0, 0.0)
    t_matrix = Matrix(bone.transform) # use blender-intern Matrix
    # the following call should set ebone.matrix to t_matrix
    # cannot do that directly, is currently wrong
    ebone.transform(t_matrix)
    print(t_matrix, ebone.matrix, sep='\n')

This gives the following output:

<Matrix 4x4 (-0.9665, -0.2564, -0.0119, 17.7405)
            ( 0.0171, -0.0183, -0.9997,  0.0280)
            ( 0.2561, -0.9664,  0.0221, -0.0120)
            ( 0.0000,  0.0000,  0.0000,  1.0000)>
<Matrix 4x4 (-0.0530, -0.0119,  0.9985, 17.7405)
            ( 0.0227, -0.9997, -0.0107,  0.0280)
            ( 0.9983,  0.0221,  0.0533, -0.0120)
            ( 0.0000,  0.0000,  0.0000,  1.0000)>

Notice how the translation is correct but I don't understand why the other part isn't. Can anyone give an insight what is going on and provide a solution to my problem?

$\endgroup$
  • $\begingroup$ Not sure if it helps, but doesn't the standard bone point in the z-direction? In my oppinion the tail should be set to (0,0,1). $\endgroup$ – maddin45 May 12 '14 at 11:56
  • $\begingroup$ I checked and no, (0, 1, 0) gives bone.matrix == Matrix.Identity(4) $\endgroup$ – WorldSEnder May 12 '14 at 11:59
  • 1
    $\begingroup$ .matrix can't be set, you need to set .roll and the both endpoints instead. There are 2 utility function: blender.org/documentation/blender_python_api_2_70a_release/… $\endgroup$ – CoDEmanX May 12 '14 at 16:32
  • $\begingroup$ Can you please explain a little bit more? Is there a method to calculate the Eigen-vector of a matrix or do I have to write that on my own? Feel free to post a full answer. $\endgroup$ – WorldSEnder May 12 '14 at 16:37
  • $\begingroup$ I don't know anything about Eigen-vectors, but aren't the available methods sufficient? There's also EditBone.transform() and Object.convert_space(). $\endgroup$ – CoDEmanX May 13 '14 at 19:20
1
$\begingroup$

bone.transform() also didn't work very well for me, so I used a method I found in a blenderartists post to extract the roll and tail from a matrix:

def vec_roll_to_mat3(vec, roll):
    target = Vector((0, 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 = Matrix.Rotation(theta, 3, axis)
    else:
        updown = 1 if target.dot(nor) > 0 else -1
        bMatrix = Matrix.Scale(updown, 3)

        # C code:
        #bMatrix[0][0]=updown; bMatrix[1][0]=0.0;    bMatrix[2][0]=0.0;
        #bMatrix[0][1]=0.0;    bMatrix[1][1]=updown; bMatrix[2][1]=0.0;
        #bMatrix[0][2]=0.0;    bMatrix[1][2]=0.0;    bMatrix[2][2]=1.0;
        bMatrix[2][2] = 1.0

    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

Then, to set it:

tail, roll = mat3_to_vec_roll(matrix)
bone.head = matrix.to_translation()
bone.tail = tail*boneLength + bone.head
bone.roll = roll
$\endgroup$
1
$\begingroup$

The code found in Emd4600's answer works with most, but not all bones. That code was a port of the internal C code of blender, which has meanwhile been improved. I have ported this newer, improved version 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 Emd4600's answer), 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

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