282 lines
9.3 KiB
Python
282 lines
9.3 KiB
Python
bl_info = {
|
|
"name": "OpenPose Mixamo Connect",
|
|
"author": "Your Name",
|
|
"version": (1, 0, 0),
|
|
"blender": (3, 0, 0),
|
|
"location": "View3D > Sidebar > OpenPose",
|
|
"description": "Connect OpenPose rig to Mixamo armature with bone mapping",
|
|
"warning": "",
|
|
"doc_url": "",
|
|
"category": "Rigging",
|
|
}
|
|
|
|
import bpy
|
|
import os
|
|
from bpy.types import Panel, Operator, PropertyGroup
|
|
from bpy.props import PointerProperty, StringProperty
|
|
|
|
|
|
# Bone mapping dictionary
|
|
BONE_MAP = {
|
|
'Head': 'mixamorig:Head',
|
|
'Foot_R': 'mixamorig:RightFoot',
|
|
'Leg_R': 'mixamorig:RightLeg',
|
|
'Foot_L': 'mixamorig:LeftFoot',
|
|
'Leg_L': 'mixamorig:LeftLeg',
|
|
'Pelvis_R': 'mixamorig:RightUpLeg',
|
|
'Pelvis_L': 'mixamorig:LeftUpLeg',
|
|
'UpperArm_R': 'mixamorig:RightArm',
|
|
'UpperArm_L': 'mixamorig:LeftArm',
|
|
'ForeArm_R': 'mixamorig:RightForeArm',
|
|
'ForeArm_L': 'mixamorig:LeftForeArm',
|
|
'Hand_R': 'mixamorig:RightHand',
|
|
'Hand_L': 'mixamorig:LeftHand',
|
|
'Bone.007': 'mixamorig:Spine2',
|
|
}
|
|
|
|
|
|
class OPENPOSE_OT_add_rig(Operator):
|
|
"""Add OpenPose rig to the scene from bundled blend file"""
|
|
bl_idname = "openpose.add_rig"
|
|
bl_label = "Add OpenPose Rig"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
# Get the addon directory
|
|
addon_dir = os.path.dirname(os.path.realpath(__file__))
|
|
blend_file = os.path.join(addon_dir, "openpose.blend")
|
|
blend_file = "/home/noti/dev/blender_mixamo_openpose_attach/openpose.blend"
|
|
|
|
# Check if the blend file exists
|
|
if not os.path.exists(blend_file):
|
|
self.report({'ERROR'}, f"OpenPose rig file not found: {blend_file}")
|
|
return {'CANCELLED'}
|
|
|
|
# Try to append the OpenPose rig
|
|
try:
|
|
# Get all existing objects before appending
|
|
existing_objects = set(bpy.data.objects)
|
|
|
|
# Append all objects from the blend file
|
|
with bpy.data.libraries.load(blend_file, link=False) as (data_from, data_to):
|
|
data_to.objects = data_from.objects
|
|
|
|
# Add the appended objects to the current scene
|
|
new_objects = []
|
|
for obj in data_to.objects:
|
|
if obj is not None and obj not in existing_objects:
|
|
context.collection.objects.link(obj)
|
|
new_objects.append(obj)
|
|
|
|
# Select the newly added objects
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
for obj in new_objects:
|
|
obj.select_set(True)
|
|
if obj.type == 'ARMATURE':
|
|
context.view_layer.objects.active = obj
|
|
|
|
self.report({'INFO'}, f"Added OpenPose rig: {len(new_objects)} object(s)")
|
|
return {'FINISHED'}
|
|
|
|
except Exception as e:
|
|
self.report({'ERROR'}, f"Failed to load OpenPose rig: {str(e)}")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class OPENPOSE_OT_connect_rigs(Operator):
|
|
"""Connect OpenPose rig to Mixamo armature using bone constraints"""
|
|
bl_idname = "openpose.connect_rigs"
|
|
bl_label = "Connect Rigs"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
props = context.scene.openpose_props
|
|
return (props.source_armature is not None and
|
|
props.target_armature is not None and
|
|
props.source_armature.type == 'ARMATURE' and
|
|
props.target_armature.type == 'ARMATURE')
|
|
|
|
def execute(self, context):
|
|
props = context.scene.openpose_props
|
|
source_armature = props.source_armature
|
|
target_armature = props.target_armature
|
|
|
|
if source_armature == target_armature:
|
|
self.report({'ERROR'}, "Source and target armatures must be different")
|
|
return {'CANCELLED'}
|
|
|
|
# Statistics
|
|
mapped_count = 0
|
|
missing_source = []
|
|
missing_target = []
|
|
|
|
# Loop through the bone map
|
|
for source_bone_name, target_bone_name in BONE_MAP.items():
|
|
# Get the pose bones
|
|
source_bone = source_armature.pose.bones.get(source_bone_name)
|
|
target_bone = target_armature.pose.bones.get(target_bone_name)
|
|
|
|
if not source_bone:
|
|
missing_source.append(source_bone_name)
|
|
continue
|
|
|
|
if not target_bone:
|
|
missing_target.append(target_bone_name)
|
|
continue
|
|
|
|
# Clear existing Copy Location constraints from source bone
|
|
for constraint in list(source_bone.constraints):
|
|
if constraint.type == 'COPY_LOCATION':
|
|
source_bone.constraints.remove(constraint)
|
|
|
|
# Add Copy Location constraint
|
|
cl_constraint = source_bone.constraints.new('COPY_LOCATION')
|
|
cl_constraint.target = target_armature
|
|
cl_constraint.subtarget = target_bone_name
|
|
cl_constraint.use_offset = False
|
|
|
|
mapped_count += 1
|
|
|
|
# Update scene
|
|
context.view_layer.update()
|
|
|
|
# Report results
|
|
report_msg = f"Connected {mapped_count} bones"
|
|
if missing_source:
|
|
report_msg += f" | Missing in source: {len(missing_source)}"
|
|
if missing_target:
|
|
report_msg += f" | Missing in target: {len(missing_target)}"
|
|
|
|
self.report({'INFO'}, report_msg)
|
|
|
|
if missing_source or missing_target:
|
|
print(f"\n=== OpenPose Mixamo Connect Report ===")
|
|
if missing_source:
|
|
print(f"Missing source bones: {', '.join(missing_source)}")
|
|
if missing_target:
|
|
print(f"Missing target bones: {', '.join(missing_target)}")
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OPENPOSE_OT_clear_constraints(Operator):
|
|
"""Clear all Copy Location constraints from the source armature"""
|
|
bl_idname = "openpose.clear_constraints"
|
|
bl_label = "Clear Constraints"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
props = context.scene.openpose_props
|
|
return props.source_armature is not None and props.source_armature.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
props = context.scene.openpose_props
|
|
source_armature = props.source_armature
|
|
|
|
cleared_count = 0
|
|
for bone in source_armature.pose.bones:
|
|
for constraint in list(bone.constraints):
|
|
if constraint.type == 'COPY_LOCATION':
|
|
bone.constraints.remove(constraint)
|
|
cleared_count += 1
|
|
|
|
context.view_layer.update()
|
|
self.report({'INFO'}, f"Cleared {cleared_count} constraint(s)")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class OPENPOSE_PT_main_panel(Panel):
|
|
"""Main panel for OpenPose Mixamo Connect addon"""
|
|
bl_label = "OpenPose Mixamo Connect"
|
|
bl_idname = "OPENPOSE_PT_main_panel"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = 'OpenPose'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
props = context.scene.openpose_props
|
|
|
|
# Add OpenPose Rig section
|
|
box = layout.box()
|
|
box.label(text="1. Add OpenPose Rig", icon='ARMATURE_DATA')
|
|
box.operator("openpose.add_rig", icon='ADD')
|
|
|
|
layout.separator()
|
|
|
|
# Select Armatures section
|
|
box = layout.box()
|
|
box.label(text="2. Select Armatures", icon='OBJECT_DATA')
|
|
|
|
col = box.column(align=True)
|
|
col.label(text="Source (OpenPose):")
|
|
col.prop(props, "source_armature", text="")
|
|
|
|
col.separator()
|
|
|
|
col.label(text="Target (Mixamo):")
|
|
col.prop(props, "target_armature", text="")
|
|
|
|
layout.separator()
|
|
|
|
# Connect Rigs section
|
|
box = layout.box()
|
|
box.label(text="3. Connect Rigs", icon='LINKED')
|
|
|
|
col = box.column(align=True)
|
|
col.operator("openpose.connect_rigs", icon='CON_LOCLIKE')
|
|
col.operator("openpose.clear_constraints", icon='X')
|
|
|
|
# Info section
|
|
if props.source_armature and props.target_armature:
|
|
layout.separator()
|
|
info_box = layout.box()
|
|
info_box.label(text=f"Bone mappings: {len(BONE_MAP)}", icon='INFO')
|
|
|
|
|
|
class OpenPoseProperties(PropertyGroup):
|
|
"""Property group to store addon properties"""
|
|
source_armature: PointerProperty(
|
|
name="Source Armature",
|
|
description="The OpenPose armature (will receive constraints)",
|
|
type=bpy.types.Object,
|
|
poll=lambda self, obj: obj.type == 'ARMATURE'
|
|
)
|
|
|
|
target_armature: PointerProperty(
|
|
name="Target Armature",
|
|
description="The Mixamo armature (constraint target)",
|
|
type=bpy.types.Object,
|
|
poll=lambda self, obj: obj.type == 'ARMATURE'
|
|
)
|
|
|
|
|
|
# Registration
|
|
classes = (
|
|
OpenPoseProperties,
|
|
OPENPOSE_OT_add_rig,
|
|
OPENPOSE_OT_connect_rigs,
|
|
OPENPOSE_OT_clear_constraints,
|
|
OPENPOSE_PT_main_panel,
|
|
)
|
|
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
bpy.types.Scene.openpose_props = PointerProperty(type=OpenPoseProperties)
|
|
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls)
|
|
|
|
del bpy.types.Scene.openpose_props
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|