見出し画像

[Blender][script]標準ポーズライブラリより多くの情報を.jsonに保存、適用する

Blender標準のポーズライブラリはどうやらボーンのトランスフォームしか登録出来ない様で、かなり不便です。
その他のボーンプロパティやボーンコンストレインの情報も含め、jsonファイルに保存、適用するスクリプトです。

ポーズモードでボーンを選択して、ファイルメニューに追加されたコマンド(サムネ参照)を実行するとブラウザが開いて任意のファイル名で保存出来ます。
※.blendとついていますが、保存すると.blendが.jsonに置き換わります。

適用するする時は同じくファイルメニューから実行します。

import bpy
import json
import os

def save_pose_with_constraints(filepath):
    # 拡張子が無ければ .json を付ける
    if not filepath.endswith('.json'):
        if not filepath.endswith('.blend'):
            filepath += '.json'
        else:
            filepath = filepath.replace('.blend', '.json')
    
    data = {}
    obj = bpy.context.object
    if not obj or obj.type != 'ARMATURE':
        print("Active object is not an armature.")
        return
    
    data["bones"] = {}
    
    for bone in obj.pose.bones:
        bone_data = {
            "location": list(bone.location),
            "rotation_quaternion": list(bone.rotation_quaternion),
            "rotation_euler": list(bone.rotation_euler),
            "scale": list(bone.scale),
            "constraints": [],
            "bone_properties": {  # ボーン固有のプロパティを追加
                "inherit_rotation": bone.bone.use_inherit_rotation,
                "inherit_scale": bone.bone.inherit_scale,
                "use_local_location": bone.bone.use_local_location
            }
        }
        for constraint in bone.constraints:
            constraint_data = {}
            for attr in dir(constraint):
                if not attr.startswith("_") and not callable(getattr(constraint, attr)):
                    try:
                        value = getattr(constraint, attr)
                        if isinstance(value, (int, float, str, list, tuple, bool)):
                            constraint_data[attr] = value
                    except Exception as e:
                        print(f"Skipping attribute {attr}: {e}")
            bone_data["constraints"].append({"type": constraint.type, "properties": constraint_data})
        
        data["bones"][bone.name] = bone_data
    
    with open(filepath, 'w') as f:
        json.dump(data, f, indent=4)
    print(f"Pose with constraints saved to {filepath}")

def load_pose_with_constraints(filepath):
    obj = bpy.context.object
    if not obj or obj.type != 'ARMATURE':
        print("Active object is not an armature.")
        return
    
    with open(filepath, 'r') as f:
        data = json.load(f)
    
    for bone_name, bone_data in data["bones"].items():
        if bone_name not in obj.pose.bones:
            print(f"Bone {bone_name} not found in the armature. Skipping.")
            continue
        
        bone = obj.pose.bones[bone_name]
        bone.location = bone_data["location"]
        bone.rotation_quaternion = bone_data["rotation_quaternion"]
        bone.rotation_euler = bone_data["rotation_euler"]
        bone.scale = bone_data["scale"]
        
        # ボーン固有プロパティを適用
        bone.bone.use_inherit_rotation = bone_data["bone_properties"]["inherit_rotation"]
        bone.bone.inherit_scale = bone_data["bone_properties"]["inherit_scale"]
        bone.bone.use_local_location = bone_data["bone_properties"]["use_local_location"]
        
        for constraint_data in bone_data["constraints"]:
            constraint_type = constraint_data["type"]
            existing_constraint = next((c for c in bone.constraints if c.type == constraint_type), None)
            if not existing_constraint:
                existing_constraint = bone.constraints.new(constraint_type)
            
            for attr, value in constraint_data["properties"].items():
                try:
                    setattr(existing_constraint, attr, value)
                except Exception as e:
                    print(f"Skipping setting attribute {attr}: {e}")
    print(f"Pose with constraints loaded from {filepath}")

class SavePoseOperator(bpy.types.Operator):
    bl_idname = "pose.save_with_constraints"
    bl_label = "Save Pose with Constraints"

    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        save_pose_with_constraints(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

class LoadPoseOperator(bpy.types.Operator):
    bl_idname = "pose.load_with_constraints"
    bl_label = "Load Pose with Constraints"

    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        load_pose_with_constraints(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

def menu_func(self, context):
    self.layout.operator(SavePoseOperator.bl_idname, text="Save Pose with Constraints")
    self.layout.operator(LoadPoseOperator.bl_idname, text="Load Pose with Constraints")

def register():
    bpy.utils.register_class(SavePoseOperator)
    bpy.utils.register_class(LoadPoseOperator)
    bpy.types.TOPBAR_MT_file.append(menu_func)

def unregister():
    bpy.utils.unregister_class(SavePoseOperator)
    bpy.utils.unregister_class(LoadPoseOperator)
    bpy.types.TOPBAR_MT_file.remove(menu_func)

if __name__ == "__main__":
    register()

必要なプロパティが漏れていたら追加します。

いいなと思ったら応援しよう!