# 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:

• Does armature editmode Ctrl-N -> 'Active Bone' work? – ideasman42 Mar 23 '17 at 2:35
• @ideasman42 I've tried it but still the target armature has the x pointing forward while the source have the z pointing forward. Is there a way to copy and set the roll and local orientation from another bone in another armature? – Tak Mar 23 '17 at 2:38
• Copying the roll from one armature to another should work. What is different about the armatures? (best update your Q with this info) – ideasman42 Mar 23 '17 at 2:45
• @ideasman42 blend file provided in the question – Tak Mar 23 '17 at 2:57
• Do you want the rotation to take object-mode armature object rotation into account? (please update the Q) – ideasman42 Mar 23 '17 at 2:59

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 > 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')

• Stunning, thanks! – lucky3 Sep 13 '19 at 17:43