[SOLVED] How to get parent relative armature?

Hi,

I’ve a Blender add-on, which supposed to get the armature. This works great, I got all bones with parents, positions and rotation quaternions. Here’s an output:

LowerBack True <Vector (0.0000, 0.7860, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
UpperBack False <Vector (0.0000, 1.1325, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
Neck False <Vector (0.0000, 1.4946, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
Head False <Vector (0.0000, 1.6517, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>

This is (name of the bone), (is root bone), (position), (rotation) in each line. If I load this into my renderer, everything works fine.

But I need to represent the child bones relative to their parents, so I thought I should multiply their transformation matrix by the parent’s inverse transformation matrix, right? And this works most of the time, but not always. With one particular test model, I get these strange results.

Here’s the relevant code:

            for i,ob_main in enumerate(objects):
                if ob_main.type != "ARMATURE":
                    continue
                for b in ob_main.data.bones:
                    # get the bone's transformation matrix. global_matrix turns it +Y up
                    m = global_matrix @ ob_main.matrix_world @ b.matrix_local
                    if b.parent:
                        # multiply by parent's inverse matrix
                        p = global_matrix @ ob_main.matrix_world @ b.parent.matrix_local
                        m = p.inverted() @ m
                    p = m.to_translation()
                    q = m.to_quaternion()
                    q.normalize()
                    print(b.name,b.parent is None,p,q)

If I multiply the bone’s matrix by the parent’s inverse, then I get this:

LowerBack True <Vector (0.0000, 0.7860, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
UpperBack False <Vector (0.0000, 1.0000, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
Neck False <Vector (0.0000, 1.0450, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
Head False <Vector (0.0000, 0.4533, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>

As you see, “LowerBack” which has no parent is left intact, and is correct. However “UpperBack”, which supposed to have relative coordinates, has invalid values! Because (0.0000, 0.7860, 0.0000) + (0.0000, 1.0000, 0.0000) != (0.0000, 1.1325, 0.0000)

If I dump the matrices too, it becomes obvious that multiplying with the inverse returns an invalid, almost an identity matrix, but why?

bone: LowerBack
model-space matrix <Matrix 4x4 (0.3465, 0.0000, 0.0000, 0.0000)
            (0.0000, 0.3465, 0.0000, 0.7860)
            (0.0000, 0.0000, 0.3465, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
bone relative matrix <Matrix 4x4 (0.3465, 0.0000, 0.0000, 0.0000)
            (0.0000, 0.3465, 0.0000, 0.7860)
            (0.0000, 0.0000, 0.3465, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
LowerBack True <Vector (0.0000, 0.7860, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
bone: UpperBack
model-space matrix <Matrix 4x4 (0.3465, 0.0000, 0.0000, 0.0000)
            (0.0000, 0.3465, 0.0000, 1.1325)
            (0.0000, 0.0000, 0.3465, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
parent matrix <Matrix 4x4 (0.3465, 0.0000, 0.0000, 0.0000)
            (0.0000, 0.3465, 0.0000, 0.7860)
            (0.0000, 0.0000, 0.3465, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
parent inverse <Matrix 4x4 ( 2.8859, -0.0000,  0.0000, -0.0000)
            (-0.0000,  2.8859, -0.0000, -2.2685)
            ( 0.0000, -0.0000,  2.8859, -0.0000)
            (-0.0000,  0.0000, -0.0000,  1.0000)>
bone relative matrix <Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
            (0.0000, 1.0000, 0.0000, 1.0000)
            (0.0000, 0.0000, 1.0000, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
UpperBack False <Vector (0.0000, 1.0000, 0.0000)> <Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>

My question is, what am I doing wrong? How come that matrix multiplication works almost all of the time, but not with this particular model? Where should I look? What should I change?

Thanks,
bzt

I’ve figured it out.

You have to normalize the matrix because when it’s converted into a position vector and rotation quaternion and then back into a matrix, that latter matrix isn’t the same as the original. You have to eliminate scaling and rounding errors and normalize the rotation:

# normalize matrix, decompose and recompose to eliminate errors
def matnorm(a):
    p, q, s = a.decompose()
    q.normalize()
    return Matrix.Translation(p) @ q.to_matrix().to_4x4()

If you multiply the normalized parent’s inverse with this normalized child matrix, then everything checks out:

m = matnorm(global_matrix @ ob_main.matrix_world @ b.matrix_local)
if b.parent:
    p = matnorm(global_matrix @ ob_main.matrix_world @ b.parent.matrix_local)
    m = p.inverted() @ m
# now this produces a vector+quaternion pair which can be added to the parent's no
# matter the model or if the renderer uses vector addition and quaternion multiplication
p = m.to_translation()
q = m.to_quaternion()

Cheers,
bzt