This commit is contained in:
2026-04-07 17:38:53 -07:00
parent ef1fa0b6bd
commit 019f5f76a2
40 changed files with 281 additions and 0 deletions

281
mixamo_openpose.py Normal file
View 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()