I'm writing an importer for an irregular model format. This is based on a 3dsmax script. The format stores the skeleton as a list of bones listing each bone's parents and two matrices, one called "Local Matrix" (Lmatrix) and one called "Absolute Matrix" (Amatrix).

The 3dsmax importer constructs the skeleton by creating a dummy for each bone, parenting them and setting their "transform" property to the Lmatrix . I tried doing this in blender by decomposing the Lmatrix and creating a bone for each empty, setting the tail to the parent's bone head (or the origin), setting the head to the translation and applying the rotation in pose mode. This didn't work.

I found out that using empties by setting their matrix_local to the Lmatrix gave the correct results position wise.

def createArmature(formatArmature, data):
    miniscene = {}
    for ix, bone in enumerate(armature):
        if "Bone.%03d"%ix not in miniscene:
            createNub(ix, bone, formatArmature, miniscene)
    miniscene["Bone.%03d"%255].name = '%s Armature'%processPath(data.path)
    data.armature = miniscene

def createRootNub(miniscene):
    o = bpy.data.objects.new("Bone.%03d"%255, None )
    bpy.context.scene.objects.link( o )
    o.show_wire = True
    o.show_x_ray = True

def createNub(ix, bone, armature, miniscene):
    o = bpy.data.objects.new("Bone.%03d"%ix, None )
    bpy.context.scene.objects.link( o )
    parentName = "Bone.%03d"%bone["parentId"]
    if parentName not in miniscene:
    o.parent = miniscene[parentName]
    for property in bone:
    o.matrix_local = bone["lmat"]
    o.show_wire = True
    o.show_x_ray = True
    o.show_bounds = True

However unlike 3dsMax, blender (at least as far as I've been able to find) doesn't really do rigging to a tree of empties, only armatures. So I figured out that with the empties already in the correct position creating a skeleton that simply connected the nodes should result in the correct positions.

def emptyToSkeleton(empty, armature):
    children = []
    for child in empty.children:
        ix = int(child.name.replace("Bone.",""))
        blenderBone = armature.edit_bones.new("TBone.%03d"%ix)
        blenderBone.head = Vector([empty.matrix_world[i][3] for i in range(3)])
        blenderBone.tail = Vector([child.matrix_world[i][3] for i in range(3)])
        if (blenderBone.tail - blenderBone.head).magnitude<MACHINE_EPSILON:
            blenderBone.head = blenderBone.head - Vector((0,0,MACHINE_EPSILON))
        for sonBone in emptyToSkeleton(child, armature):
            sonBone.parent = blenderBone
        for property in empty.keys():
    return children

This worked fine visually, however I suspected that rotations would be missing or completely wrong. Which I confirmed when converting back the armature to a tree of empties with the following code and seeing that the matrix_local had changed in the process.

def posedSkeletonToEmpty(bone, root):
    ix = int(bone.name.replace("TBone.",""))
    empty = bpy.data.objects.new("EBone.%03d"%ix, None )
    bpy.context.scene.objects.link( empty )
    empty.parent = root
    #empty.matrix_local = bone.matrix
    if "nub" in bone.keys():
        empty.location = bone.tail-bone.head if bone["nub"] else Vector((0,0,0))
    for property in bone.keys():
        empty[property]= bone[property]
    for child in bone.children:
        posedSkeletonToEmpty(child, empty)

Since I want to also be able to write an exporter (and eventually import animations as well) I'd like to know if there's a way to convert to correctly do this process without loss of information. I'm aware that this might be bordering into an XY problem and it might be possible to create the skeleton directly from the lmatrix without tree of empty intermediaries and then calculate the lmatrix from blender objects, or another more elegant solution I'd welcome those as well.

I can provide the entire importer codebase and sample files if necessary.


Your Answer

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

Browse other questions tagged or ask your own question.