Needed help with creating bones in python using position and rotation data

Hello everyone,

I am developing addon to import skeleton and mesh data from one game.
I was able to make more progress than I thought I’d be able without help, but now I’m facing problem with something.

I have skeleton data made in such a way, that every bone has it’s local space coordinates for head, no tail information and rotation saved as quaternion. Conversion of these values is not a big problem, but using them to transform bones realtively to their parents is what is causing problems.

In Python I was able to use EditBone to create new bone and set it’s position, but it is considering it has origin point in cetral point of scene space. I need to have bones use their parent position as it’s origin point. Also EditBone cannot be directly rotated.

That’s why I’m looking for some help. How I could use location data and quaternion data to set all bones in proper positions. Sadly format doesn’t store positions in any other space than local, so bones need to be “extruded” from parent bones (not considering first one, that is root, because it has it’s local space same as screen space).

I’ll be grateful for any help you can provide.

1 Like

Use this code as example

import bpy
from mathutils import Vector, Quaternion
context = bpy.context

bones = {}
# XYZ coordinates of bone head in armature space, WXYZ rotation in quaternions 
bones["Bone"] = ((-1.48222, -0.194431, -0.383472), (0.953425, 0.01901, -0.300824, 0.011176))
# XYZ coordinates of bone head in parent bone space, WXYZrotation in quaternions
bones["Bone.001"] = ((0, 3.3363, 0), (0.35458, -0.269697, 0.157483, -0.881326))
# XYZ coordinates of bone head in parent bone space, WXYZ rotation in quaternions
bones["Bone.002"] = ((0, 4.0228, 0), (0.074972, -0.432144, 0.202091, -0.875666))
# XYZ coordinates of bone head in parent bone space, WXYZ rotation in quaternions
bones["Bone.002_nub"] = ((0, 4.7955, 0), (1, 0, 0, 0))

armature = bpy.data.armatures.new("Armature")
rig = bpy.data.objects.new("Armature", armature)
context.scene.collection.objects.link(rig)

context.view_layer.objects.active = rig
bpy.ops.object.editmode_toggle()

for i, bone in enumerate(bones.items()):
    # create new bone
    current_bone = armature.edit_bones.new(bone[0])
    
    # first bone in chain
    if i == 0:
        # create bone at armature origin and set its length
        current_bone.head = [0, 0, 0]
        length = list(bones.values())[i+1][0][1]
        current_bone.tail = [0, 0, length]

        # rotate bone
        quat_armature_space = Quaternion(bone[1][1])
        current_bone.transform(quat_armature_space.to_matrix())
        
        # set position
        current_bone.translate(Vector(bone[1][0]))

        # save bone, its tail position (next bone will be moved to it) and quaternion rotation
        parent_bone = current_bone
        parent_bone_tail = current_bone.tail
        parent_bone_quat_armature_space = quat_armature_space
        
    # last bone in chain
    elif i == (len(bones) - 1):
        # create bone at armature origin and set its length
        current_bone.head = [0, 0, 0]
        current_bone.tail = [0, 0, 1]
        
        # rotate bone
        current_bone_quat_parent_space = Quaternion(bone[1][1])
        # like matrices, quaternions can be multiplied to accumulate rotational values
        transform_quat = parent_bone_quat_armature_space @ current_bone_quat_parent_space
        current_bone.transform(transform_quat.to_matrix())

        # set position
        current_bone.translate(Vector(parent_bone_tail))
        
        # connect
        current_bone.parent = parent_bone
        current_bone.use_connect = True
        
    else:
        # create bone at armature origin and set its length
        current_bone.head = [0, 0, 0]
        length = list(bones.values())[i+1][0][1]
        current_bone.tail = [0, 0, length]
        
        # rotate bone
        current_bone_quat_parent_space = Quaternion(bone[1][1])
        # like matrices, quaternions can be multiplied to accumulate rotational values
        transform_quat = parent_bone_quat_armature_space @ current_bone_quat_parent_space
        current_bone.transform(transform_quat.to_matrix())
        
        # set position
        current_bone.translate(Vector(parent_bone_tail))
        
        # connect
        current_bone.parent = parent_bone
        current_bone.use_connect = True
        
        # save bone, its tail position (next bone will be moved to it) and quaternion rotation
        parent_bone = current_bone
        parent_bone_tail = current_bone.tail
        parent_bone_quat_armature_space = transform_quat

bpy.ops.object.editmode_toggle()
1 Like

The easiest way is using 4x4 matrices. You can construct a local space matrix easily from your input location and quaternion
mat_local = quat.to_matrix().to_4x4()
mat_local .translation = loc

To get armature space matrices from local space matrices, you multiply all parent bones’ matrices up to the root bone in the right order.
mat_armature = mat_local * parent_mat_local_0 * parent_mat_local_1 * … * parent_mat_local_n
The parent mats are the direct parent first, more removed parents in the end.
You can also save some calculations if you traverse your bones and store the armature space matrices in a dict.
armature_mat = local_mat * mat_dic[bone.parent.name]
mat_dic[bone.name] = armature_mat

To create a bone from the final armaturespace matrix, see my answer here:
https://blender.stackexchange.com/questions/9318/set-a-bones-matrix-to-a-custom-matrix/90240#90240

Thanks for your reply, I tried with matrices once again, but currently I don’t see how this could even work. Armature space matrix for bone is 4x4 matrix. Functions you provided from blender source are for 3x3 matrices, Am I missing something? Because I don’t know how to exactly transform 4x4 matrix to 3x3. Does to_3x3 function from matrix object convert it properly?

Edit: to_3x3 solved problem and I now got something that looks like that skeleton, but it is still not calculated properly.

To make things clear I have data like this:

Position [x,y,z] in space of the parent. It uses parent axes that are based on it’s rotation.
Rotation of given bone. For now I’m not entirely sure if this rotation is added to rotation of parent, or simply rotates once again. I can share screens from original software that allows to view these models, but is not going to allow editing or anything like that, and for comparison my skeleton from blender, which has some bones rotated in totally wrong direction, and some look like they’ve been mirrored.

Sadly I don’t have much info about that format and everything I know is based on binary model files I have.

This is how you apply the 4x4 matrix b_bind (armature space) to the edit bone:

tail, roll = nif_utils.mat3_to_vec_roll(b_bind.to_3x3())
b_edit_bone.head = b_bind.to_translation()
b_edit_bone.tail = tail + b_edit_bone.head
b_edit_bone.roll = roll

Thanks again for your reply. I figued that out already.

I was digging through format and couldn’t find anything wrong with calculations. What happened is that game developer used a little trick for quaternions, and W part of them is xored with specific value.
That caused a lot of problems with bones. I figured that out when disassembling part of game code.

Thanks for your help. It is all now working :smiley:

1 Like

It isn’t working for me. Can you take a look? I’m using empties instead of a dictionary of points and rotations. My source data sounds very similar to the original post in that way, at least as far as the coordinates. The lengths of my bones don’t matter, since the offsets must remain to one empty from its parent. Neither the bone positions nor tails (which are transformed based on rotation of a corresponding Empty with a parent) are in the correct place. I am using your code here and also tried the blender.stackexchange.com instructions (blender says “ValueError: Matrix.to_translation(): inappropriate matrix size” in that case even though I do to_3x3()). If I do the method on here instead (using rotation_quaternion directly), the bones are rotated and moved in unexpected ways, overall roughly the size of the original hierarchy of empties but rotated (even though the “root” bone matches the rotation of the “root.Empty” perfectly and I’ve tried cumulative rotation and other things).

Steps to reproduce: