Changes
This commit is contained in:
281
mixamo_openpose.py
Normal file
281
mixamo_openpose.py
Normal file
@@ -0,0 +1,281 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user