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()
|
||||
BIN
my_animation_openpose_se_0024.png
Normal file
BIN
my_animation_openpose_se_0024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
my_animation_openpose_se_0025.png
Normal file
BIN
my_animation_openpose_se_0025.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
my_animation_se_0025.png
Normal file
BIN
my_animation_se_0025.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
my_animation_se_0029.png
Normal file
BIN
my_animation_se_0029.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
openpose.blend
Normal file
BIN
openpose.blend
Normal file
Binary file not shown.
BIN
openpose.blend1
Normal file
BIN
openpose.blend1
Normal file
Binary file not shown.
BIN
openpose.blend2
Normal file
BIN
openpose.blend2
Normal file
Binary file not shown.
BIN
openpose.blend3
Normal file
BIN
openpose.blend3
Normal file
Binary file not shown.
BIN
openpose.blend4
Normal file
BIN
openpose.blend4
Normal file
Binary file not shown.
Reference in New Issue
Block a user