assigning matrices to EditBones in 2.5/2.6 [solution]

EDIT:
I’ve found the solution.
Quickly what this topic was about:
In new Blender API edit_bone.matrix is read-only, unlike before. So creating bones from transform matrices is not possible, unless writing complex math yourself. We tried porting Blender’s C code for doing that to Python, but it didn’t work 100%. Using the Blender 2.4 code instead worked.

The solution seems to be to do what the old Blender 2.4 API did. So again seems like a bug in Blender 2.6. I’ve tried letting the blender devs know about this enough, my script works and I’m happy.
Code:


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)
        
        # 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 = 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

usage:


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

================================================
orig post:

The new Blender API doesn’t have a way to create correct bones from transform matrices, to my surprise.
After days of digging, I found the only way is to use complex matrix math yourself, for example port the Blender C functions to Python.

The functions are:


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

usage:


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

This is still not a 100% correct solution, some bones are still wrong compared to Blender 2.4. The problem seems to come from this line:

if axis.dot(axis) > 0.0000000001:

How to solve it? I don’t know and looks like even Ton isn’t sure:
https://svn.blender.org/svnroot/bf-blender/trunk/blender/source/blender/blenkernel/intern/armature.c

But there must be solution, otherwise how does Blender 2.4 do it correctly, when it uses the same bone base, tip and roll thing?

So help me get this fixed, it will make my importer work properly, as well as Blender itself.

If i remember correctly they reversed something in matrixes … I am no expert , but yes it should be diffirent so the behavior you are getting should be normal. Take a look at the api and make sure you use matrixes correctly.

http://www.blender.org/documentation/blender_python_api_2_62_release/mathutils.geometry.html

http://www.blender.org/documentation/blender_python_api_2_62_release/mathutils.html

By all honesty looks like you didn’t get anything :confused:

I am told that frequently , who knows it may be true :smiley:

That seems like a really weird case to me. “axis.dot(axis) > eplison” is testing for bones that are effectively 0-length. Do you have 0-length bones in your model (i.e. head = tail)?

I highlighted the problematic bone with weird rotation in the image with a red dot. I’ve posted its matrix too.

It doesn’t look zero sized in Blender, and printing matrixFromFile.to_scale() on each bone matrix returns (1,1,1), so probably not.

BTW, if I use lower or higher precision in that check other bones break their animations too.

Im really out of places I can go for help.

Will maybe posting a simplified model/anim importer with a sample model help? I’ve posted the problematic matrix but maybe there’s something else going on.

Unfortunately, I don’t know enough about the math for what this vector+roll is. It seems like this case is just not being handled correctly based on reading the C-code. They tried a bunch of different ‘eplison’ values and each one fixed an issue but introduced a new bug.

This zero length axis can only happen if the input vector is length 0 or if it is parallel to the vector (0, 1, 0). My best guess is that the choice of ‘eplison’ value is selecting a ‘roll’ of 0 or 180 degrees and that some models work correctly and some don’t. Maybe this calculation is just non-deterministic for automatically determining 0 or 180 degree roll in all cases.

Well at least you know something.
I tried to find the source for Blender 2.4, here: https://svn.blender.org/svnroot/bf-blender/branches/blender-2.47/source/blender/blenkernel/intern/armature.c
Is there any difference in the functions? By assigning the edit bone matrix in 2.4 directly everything worked fine.

The bone I have is pointing at (0,-1,0) and there are few more like that. I could add an exception in the function for these kinds of bones, but what calculation to use then?

some remark:

the dot-product makes only sense for normalized vectors - every other kind of vector may easy introduce some quirks for too small or too large values.

and last, always remember a float calculation may never return with a correct result, if a result is very close to zero, it might easy swap the sign … and everything based on the sign may freak out then …

I don’t know which dot() call you mean, I tried both with normalized() with no difference.

And I’m not sure about your info on floating point. I know they have a precision limit, but not sure if I’m passing it.
Either way using the matrices in Blender 2.4 worked and apparently Blender did this calculation itself.

Blender doesn’t handle zero length bones, your import / export format however, may. Consider 1 divided by ten to the power of 15 to be zero. A number of bvh files I use have zero length bones, have a look how the bvh importer handles them. If you are using the bvh rig for an armature modifier my suggestion is to not deform with them.

Your choice of how to handle them is arbitrary. This part


        updown = 1 if target.dot(nor) > 0 else -1
        bMatrix = mathutils.Matrix.Scale(updown, 3)

eg if the angle between your normalized vector and target (0,1,0) is less than 90degrees it is given a scale matrix made of 1s and greater than zero a scale matrix of -1 … or as it’s named, up down as to whether it is above or below the xz plane. Down will give you the bones you say are pointing at (0,-1,0). The solution could be as simple as flipping the test. Hard to know without posting some code / sample files, which on your previous posts it seems you have varying opinions on.

I don’t think any bones in my files are zero sized. They aren’t zero sized in Blender’s 3d view, otherwise they wouldn’t look like a stick, but a point and would get removed by Blender. Their scale part in their matrix is always (1,1,1). Isn’t that what you mean by zero-sized? if not, can you explain those to me?
I didn’t write that code, are you saying that part checks if a bone is zero sized or not?

As for sample code, look in first post, “usage:”. I’ve also posted the matrix for that bone, that’s not the matrix in Blender, but the one in the file. Isn’t that what you need? If not, can you explain what information you’re looking for exactly so I’ll try to simplify my scripts accordingly before posting them? I thought you need the bone data from the file and bone reading part from the code.

Oh really?

Their scale part in their matrix is always (1,1,1). Isn’t that what you mean by zero-sized? if not, can you explain those to me?
I didn’t write that code, are you saying that part checks if a bone is zero sized or not?

Here is a pic of an armature created from a bvh 10_34 found from http://sites.google.com/a/cgspeed.com/cgspeed/motion-capture/cmu-bvh-conversion. The bvh format has a format that contains hierarchy information of the skeleton and animation data. The bones that are green are zero length in the bvh data. They are “handled” by the importer, in this case I’ve made them head - tail = (0,1,0) and green.


I asked you to explain me if my bone is zero sized, because it doesn’t look like that to me. You have just shown that bvh format can have zero sized bones, and your importer has a workaround for that. How is that related? What have you misread?

I’ve posted the matrix of my bone.
Here’s getting the scale from it:


import bpy
import mathutils

mymatrix = mathutils.Matrix(((1.0000, -0.0000,  0.0000, -4.4836),
    (0.0000, -1.0000, -0.0000,  0.1659),
    (0.0000,  0.0000, -1.0000, 11.0056),
    (0.0000,  0.0000,  0.0000,  1.0000)))

print(mymatrix.to_scale())

I get (1,1,1).

If that isn’t what makes it “zero-sized”, then again please explain to me what does.

And you didn’t specify what additional info you need. You asked for example file and code, I’ve posted an example (actually the only problematic) matrix from my file and the code for processing it.

You can use common sense and not assume the bones are zero sized, after I’ve explained why they clearly aren’t (twice already!) and by the fact that Blender would delete the zero sized bones and I wouldn’t ever be able to pose them (the image), not be a total ass when explained that your assumptions are wrong in a normal way. Actually I noticed that negative attitude of yours before. Is it because of my example gif? I said I wasn’t trying to be rude and you said “don’t worry”. What’s your problem?

Column 1 of your matrix is (0,-1,0) which is parallel to the chosen target and gives a cross product with it of (0,0,0) for axis which will then fail the axis.dot(axis) test,after which plus or minus the identity matrix is used for the bmatrix part of your calculation, and with roll=0 passed you will always get plus or minus (0,1,0) for your bone offset with every matrix where column 1 is a vector parallel to (0,1,0).
Thanks. We’ll try to figure out a solution knowing the cause now that you are tired of “my bullshit”.

I just had another thought about this issue. Maybe you need to add more cases to your conversion code. Right now you can fall into the “0 case” under 3 conditions:

  • ‘vec’ approaches (0,0,0), which you say is not happening in your model.
  • ‘vec’ approaches (0, 1, 0) in armature space
  • ‘vec’ approaches (0, -1, 0) in armature space

Right now, all three cases are treated the same. Maybe you just need different cases for the last 2 conditions.

Hm, OK.
That’s not my code, someone converted it from Blender’s C code (the stackoverflow link) and I’m just using it.

So while I feel comfortable in Python, I have no idea what math to use in those additional “if” blocks you suggest. Any ideas?

Nevermind, the 0 case does change ‘updown’ based on which direction the vector is pointing.

  1. The file format we use stores the 4x4 matrices, not head/tail positions.
    More or less all matrices have scale (1,1,1) so they’re not zero sized.
  2. Everything used to work in 2.4 since we could just put in the matrices directly into editbones.
  3. This code was found that should convert the matrices into something usable by 2.5+
  4. However, the way it does the calculation leads to bones that are parallell to a certain
    vector behaving strangely, with some people calling them “zero sized” and muddling the
    issue if I understand the comments correctly.

So we need either a completely different conversion code that doesn’t have this “gimbal lock”-like issue, or a proper way to handle these corner case bones in the current code.
Is there ANYONE here knowledgeable enough about this stuff?