Blender python 骨を揃える

やあ

ブレンダーでキャラクターをいっぱい作った後にちょっと骨の位置を修正したいなーってなったときに書いた、骨を特定のアーマチュアに揃えるスクリプトだよ。

揃える元のアーマチュアがある場合

import bpy
from typing import Dict, List

def get_obj(name : str):
    return bpy.data.objects.get(name)

def get_children(result: Dict, target_bone) -> List:
    if len(target_bone.children) == 0:
        return []
    child_name = []
    next_tasks = []
    for child in target_bone.children:
        next_tasks.append(child)
        child_name.append(child.name)
    if target_bone.name in result:
        result[target_bone.name] += child_name
    else:
        result[target_bone.name] = child_name
    for child in next_tasks:
        get_children(result, child)
        
bpy.ops.object.mode_set(mode='OBJECT')
    
"""
---- ソースオブジェクト ----
"""
source_obj_name = "source_armature_name"
source_armature = get_obj(source_obj_name)
source_armature.select = True

root_bone_name = "Root"

"""
---- 処理をしたいオブジェクトの名前
"""
target_obj_names = [
"Armature_A",
"Armature_B"
]

"""
---- 処理をしたくない骨の親の名前 ----
例 : 
親 : Hand
子 : Thumb, Index, Middle, Ring, Pinky
となっているときに、
Handを追加すると、子が無視されます

"""
ignore_bones_parent_names = [
]

"""
---- 無視したい骨の名前 ----
"""
ignore_bone_names = [
]

"""
---- 無視したい骨の名前の一部 ----
"""
ignore_part_of_names = [
    "IK_", "C_"
]

"""
---- トランスフォーム : ヘッドを無視する骨の名前 ----
"""
ignore_head_bone_names = [
]

"""
---- トランスフォーム : テイルを無視する骨の名前 ----
"""
ignore_tail_bone_names = [
]

"""
---- トランスフォーム : ロールを無視する骨の名前 ----
"""
ignore_roll_bone_names = [
]

is_debug = True

for target_obj_name in target_obj_names:
    target_armature = get_obj(target_obj_name)
    if target_armature:
        if is_debug:
            print(target_armature)
        target_armature.select = True
        bpy.ops.object.mode_set(mode='EDIT')
        root_bone = target_armature.pose.bones.get(root_bone_name)
        if root_bone:
            children = dict()
            get_children(children, root_bone)
            for parent_name, childs in children.items():
                if parent_name in ignore_bones_parent_names:
                    continue
                for bone_name in childs:
                    if bone_name in ignore_bone_names:
                        continue
                    is_found_ignore_bone = False
                    for ignore_part_of_name in ignore_part_of_names:
                        if ignore_part_of_name in bone_name:
                            is_found_ignore_bone = True
                            break
                    if is_found_ignore_bone:
                        continue
                    target_bone = target_armature.data.edit_bones.get(bone_name)
                    if target_bone:
                        source_align_bone = source_armature.data.edit_bones.get(bone_name)
                        if source_align_bone:
                            if parent_name not in ignore_head_bone_names:
                                target_bone.head = [source_align_bone.head[0], source_align_bone.head[1], source_align_bone.head[2]]
                            if parent_name not in ignore_tail_bone_names:
                                target_bone.tail = [source_align_bone.tail[0], source_align_bone.tail[1], source_align_bone.tail[2]]
                            if parent_name not in ignore_roll_bone_names:
                                target_bone.roll = source_align_bone.roll
        else:
            if is_debug:
                print("root bone is None at : " + target_armature.name)
        bpy.ops.object.mode_set(mode='OBJECT')
        target_armature.select = False
    else:
        if is_debug:
            print("Target armature is None at : " + target_obj_name)
    bpy.ops.object.mode_set(mode='OBJECT')

# 指定するのが面倒だから全オブジェクトについて処理したい場合

import bpy
from typing import Dict, List

def get_obj(name : str):
    return bpy.data.objects[name]

def get_children(result: Dict, target_bone) -> List:
    if len(target_bone.children) == 0:
        return []
    child_name = []
    next_tasks = []
    for child in target_bone.children:
        next_tasks.append(child)
        child_name.append(child.name)
    if target_bone.name in result:
        result[target_bone.name] += child_name
    else:
        result[target_bone.name] = child_name
    for child in next_tasks:
        get_children(result, child)
        
bpy.ops.object.mode_set(mode='OBJECT')

"""
---- ソースオブジェクト ----
"""
source_obj_name = "Source_Armature"
 
source_armature = get_obj(source_obj_name)
source_armature.select = True

root_bone_name = "Root"

"""
---- 処理をしたくない骨の親の名前 ----
例 : 
親 : Hand
子 : Thumb, Index, Middle, Ring, Pinky
となっているときに、
Handを追加すると、子が無視されます

"""
ignore_bones_parent_names = [
]

"""
---- 無視したい骨の名前 ----
"""
ignore_bone_names = [
]

"""
---- 無視したい骨の名前の一部 ----
"""
ignore_part_of_names = [
    "IK_", "C_"
]

"""
---- トランスフォーム : ヘッドを無視する骨の名前 ----
"""
ignore_head_bone_names = [
]

"""
---- トランスフォーム : テイルを無視する骨の名前 ----
"""
ignore_tail_bone_names = [
]

"""
---- トランスフォーム : ロールを無視する骨の名前 ----
"""
ignore_roll_bone_names = [
]

is_debug = True

for obj in bpy.data.objects:
    if obj.type == "ARMATURE":
        target_armature = get_obj(obj.name)
        if target_armature:
            if is_debug:
                print(target_armature)
            target_armature.select = True
            bpy.ops.object.mode_set(mode='EDIT')
            root_bone = target_armature.pose.bones.get(root_bone_name)
            if root_bone:
                children = dict()
                get_children(children, root_bone)
                for parent_name, childs in children.items():
                    if parent_name in ignore_bones_parent_names:
                        continue
                    for bone_name in childs:
                        if bone_name in ignore_bone_names:
                            continue
                        is_found_ignore_bone = False
                        for ignore_part_of_name in ignore_part_of_names:
                            if ignore_part_of_name in bone_name:
                                is_found_ignore_bone = True
                                break
                        if is_found_ignore_bone:
                            continue
                        target_bone = target_armature.data.edit_bones.get(bone_name)
                        if target_bone:
                            source_align_bone = source_armature.data.edit_bones.get(bone_name)
                            if source_align_bone:
                                if parent_name not in ignore_head_bone_names:
                                    target_bone.head = [source_align_bone.head[0], source_align_bone.head[1], source_align_bone.head[2]]
                                if parent_name not in ignore_tail_bone_names:
                                    target_bone.tail = [source_align_bone.tail[0], source_align_bone.tail[1], source_align_bone.tail[2]]
                                if parent_name not in ignore_roll_bone_names:
                                    target_bone.roll = source_align_bone.roll
            else:
                if is_debug:
                    print("root bone is None " + target_armature.name)
            bpy.ops.object.mode_set(mode='OBJECT')
            target_armature.select = False
        else:
            if is_debug:
                print("Target armature is None " + obj.name)
        bpy.ops.object.mode_set(mode='OBJECT')

値を入力して揃える

import bpy
align_bone_name = "Hand_L"

target_obj_names = [
"Armature_A",
"Armature_B"
]
def get_obj(name : str):
    return bpy.data.objects.get(name)
    
source_align_bone_head = [-1.3189, 0.1459, 1.9255]
source_align_bone_tail = [-1.3189, -0.2293, 1.9255]
source_align_bone_roll = 0.0

if source_align_bone_head == source_align_bone_tail:
    print("""
        -------------- Warning. --------------
        
        The head vector matches the tail vector.
        This will cause align_bone to be removed.
        
        So, this script is cancelled.
        --------------------------------------
    """)
else:
    for target_obj_name in target_obj_names:
        target_armature = get_obj(target_obj_name)
        if target_armature:
            target_armature.select = True
            print(target_armature)
            bpy.ops.object.mode_set(mode='EDIT')
            target_bone = target_armature.data.edit_bones.get(align_bone_name)
            if target_bone:
                target_bone.head = source_align_bone_head
                target_bone.tail = source_align_bone_tail
                target_bone.roll = source_align_bone_roll
            bpy.ops.object.mode_set(mode='OBJECT')
            target_armature.select = False