How to match bone orientation between two objects in Python? I have two armatures, I want to copy the roll value of each bone from the Source armature to each bone in the Target armature and also have the local orientation correctly. The two armatures have the same number of bones but with slightly different locations and sometimes the source armature object is rotated.

I've already done copying the roll value using the below script but I wonder now how I can copy the bone local orientation as well so that the bones will have the same roll value as well as the same local orientation cause some of the bones has different local orientation as shown below:



import bpy

target = bpy.data.objects['Target']
source = bpy.data.objects['Source']


for pb in source.pose.bones:
    pb.rotation_mode = 'YXZ'

for pb in target.pose.bones:
    pb.rotation_mode = 'YXZ'

sourceroll = {}
bpy.ops.object.select_all(action='DESELECT')
source.select = True
bpy.context.scene.objects.active = source
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.armature.select_all(action='SELECT')

for b in source.data.edit_bones:
    sourceroll.update({b.name:b.roll})

bpy.ops.object.mode_set(mode='OBJECT')

bpy.ops.object.select_all(action='DESELECT')
target.select = True
bpy.context.scene.objects.active = target
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.armature.select_all(action='SELECT')

for b in target.data.edit_bones:
    b.roll = sourceroll[b.name]

bpy.ops.object.mode_set(mode='OBJECT') 


My .blend file can be found here:
First of all, if you have 2 armatures with different object rotations relative to each-other, simply copying the roll value won't give you (visually) the same orientation (or if it does it will only be by accident).

The basic solution to this is:


Get an orthogonal direction from the bone (X or Z axis).
Transform that axis into world-space.
Transform the axis into the local space of the other object.
Algin all bones (X or Z) to that axis.


This is a working script written by @Tak, updates by @ideasman42:

import bpy
import math

def align_bone_x_axis(edit_bone, new_x_axis):
    """ new_x_axis is a 3D Vector the edit_bone's x-axis will point towards.
    """
    new_x_axis = new_x_axis.cross(edit_bone.y_axis)
    new_x_axis.normalize()
    dot = max(-1.0, min(1.0, edit_bone.z_axis.dot(new_x_axis)))
    angle = math.acos(dot)
    edit_bone.roll += angle
    dot1 = edit_bone.z_axis.dot(new_x_axis)
    edit_bone.roll -= angle * 2.0
    dot2 = edit_bone.z_axis.dot(new_x_axis)
    if dot1 &gt; dot2:
        edit_bone.roll += angle * 2.0

source = bpy.data.objects['Source']
target = bpy.data.objects['Target']

# Needed to account for object rotation
source_to_world_matrix = source.matrix_world.to_3x3()
world_to_target_matrix = target.matrix_world.inverted().to_3x3()

source_x_axis = {}
bpy.ops.object.select_all(action='DESELECT')
source.select = True
bpy.context.scene.objects.active = source
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.armature.select_all(action='SELECT')

for b in source.data.edit_bones:
    source_x_axis[b.name] = source_to_world_matrix * b.x_axis

bpy.ops.object.mode_set(mode='OBJECT')

bpy.ops.object.select_all(action='DESELECT')
target.select = True
bpy.context.scene.objects.active = target
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.armature.select_all(action='SELECT')

for b in target.data.edit_bones:
    align_bone_x_axis(b, world_to_target_matrix * source_x_axis[b.name])

bpy.ops.object.mode_set(mode='OBJECT')