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()