From b015736eadee6af1bd7ba365191bf38e543b2719 Mon Sep 17 00:00:00 2001 From: 989onan Date: Fri, 13 Sep 2024 12:38:08 -0400 Subject: [PATCH] fix merge armatures now the different options work. and it has an apply transforms --- core/common.py | 103 ++++++++++++++++++++ core/properties.py | 15 ++- functions/armature_modifying.py | 152 ++++++++++-------------------- resources/translations/en_US.json | 4 + ui/merge_armatures.py | 14 ++- 5 files changed, 180 insertions(+), 108 deletions(-) diff --git a/core/common.py b/core/common.py index cb9562c..c4e06fa 100644 --- a/core/common.py +++ b/core/common.py @@ -115,6 +115,9 @@ def get_armature(context: Context, armature_name: Optional[str] = None) -> Optio def get_armatures(self, context: Context) -> List[Tuple[str, str, str]]: return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'ARMATURE'] +def get_armatures_that_are_not_selected(self, context: Context) -> List[Tuple[str, str, str]]: + return [(obj.name, obj.name, "") for obj in bpy.data.objects if ((obj.type == 'ARMATURE') and (obj.name != context.scene.selected_armature))] + def get_selected_armature(context: Context) -> Optional[Object]: if context.scene.selected_armature: armature = bpy.data.objects.get(context.scene.selected_armature) @@ -141,6 +144,106 @@ def select_current_armature(context: Context) -> bool: return True return False +def apply_pose_as_rest(context: Context, armature_obj: bpy.types.Object, meshes: list[bpy.types.Object]) -> bool: + for obj in meshes: + mesh_data: Mesh = obj.data + + if mesh_data.shape_keys: + shape_key_obj_list: list[bpy.types.Object] = [] + modifier_armature_name: str = "" + + for modifier in obj.modifiers: + if modifier.type == "ARMATURE": + arm_modifier: bpy.types.ArmatureModifier = modifier + if not (arm_modifier.object == armature_obj): + continue + modifier_armature_name = arm_modifier.object.name + + if modifier_armature_name == "": + continue + for idx,shape in enumerate(mesh_data.shape_keys.key_blocks): + if idx == 0: + continue + context.view_layer.objects.active = obj + + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.select_all(action="DESELECT") + context.view_layer.objects.active = obj + obj.select_set(True) + + #create duplicate of object + bpy.ops.object.duplicate() + + shape_obj = context.view_layer.objects.active + + #make current shapekey a separate object + shape_obj.active_shape_key_index = idx + shape_obj.name = shape.name + + bpy.ops.object.shape_key_move(type="TOP") + + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.object.mode_set(mode="OBJECT") + + bpy.ops.object.shape_key_remove(all=True) + + bpy.ops.object.modifier_apply(modifier=modifier_armature_name) + + #for modifier_name in [i.name for i in shape_obj.modifiers]: + # bpy.ops.object.modifier_remove(modifier=modifier_name) + + shape_key_obj_list.append(shape_obj) #add to a list of shape key objects + context.view_layer.objects.active = obj + + bpy.ops.object.mode_set(mode="OBJECT") + context.view_layer.objects.active.select_set(True) + bpy.ops.object.shape_key_remove(all=True) + bpy.ops.object.modifier_apply(modifier=modifier_armature_name) + bpy.ops.object.select_all(action="DESELECT") + + for shapekey_obj in shape_key_obj_list: + shapekey_obj.select_set(True) + context.view_layer.objects.active = obj + context.view_layer.objects.active.select_set(True) + + try: + bpy.ops.object.join_shapes() + except: + + #delete shapekey objects to not leave ourselves in a bad exit state - @989onan + context.view_layer.objects.active = shape_key_obj_list[0] + obj.select_set(False) + bpy.ops.object.delete(confirm=False) + return False + context.view_layer.objects.active = shape_key_obj_list[0] + obj.select_set(False) + bpy.ops.object.delete(confirm=False) + else: + modifier_armature_name: str = "" + + for modifier in obj.modifiers: + if modifier.type == "ARMATURE": + arm_modifier: bpy.types.ArmatureModifier = modifier + if not (arm_modifier.object == armature_obj): + continue + modifier_armature_name = arm_modifier.object.name + + if modifier_armature_name == "": + continue + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.select_all(action="DESELECT") + context.view_layer.objects.active.select_set(True) + bpy.ops.object.modifier_apply(modifier=modifier_armature_name) + + context.view_layer.objects.active = armature_obj + armature_obj.select_set(True) + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.mode_set(mode="POSE") + + bpy.ops.pose.armature_apply(selected=False) + return True + def get_all_meshes(context: Context) -> List[Object]: armature = get_selected_armature(context) if armature and is_valid_armature(armature): diff --git a/core/properties.py b/core/properties.py index 4e0e69a..92e5ea6 100644 --- a/core/properties.py +++ b/core/properties.py @@ -4,7 +4,7 @@ from ..core.register import register_property from bpy.types import Scene, Object, Material, Context from bpy.props import BoolProperty, EnumProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty from ..core.addon_preferences import get_preference -from ..core.common import SceneMatClass, MaterialListBool, get_armatures, get_mesh_items +from ..core.common import SceneMatClass, MaterialListBool, get_armatures, get_mesh_items, get_armatures_that_are_not_selected def register() -> None: default_language = get_preference("language", 0) @@ -23,10 +23,21 @@ def register() -> None: ))) register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty( - items=get_armatures, + items=get_armatures_that_are_not_selected, name=t("MergeArmatures.selected_armature.label"), description=t("MergeArmatures.selected_armature.label") ))) + + register_property((bpy.types.Scene, "merge_armature_apply_transforms", bpy.props.BoolProperty( + default=False, + name=t("MergeArmature.merge_armatures.apply_transforms.label"), + description=t("MergeArmature.merge_armatures.apply_transforms.desc") + ))) + register_property((bpy.types.Scene, "merge_armature_align_bones", bpy.props.BoolProperty( + default=False, + name=t("MergeArmature.merge_armatures.align_bones.label"), + description=t("MergeArmature.merge_armatures.align_bones.desc") + ))) register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False))) diff --git a/functions/armature_modifying.py b/functions/armature_modifying.py index b77e67f..34fde6b 100644 --- a/functions/armature_modifying.py +++ b/functions/armature_modifying.py @@ -4,6 +4,8 @@ from bpy.types import Context, Mesh, Panel, Operator, Armature, EditBone from ..functions.translations import t from ..core.common import get_selected_armature, get_all_meshes from ..core import common +from ..core.dictionaries import bone_names +from mathutils import Matrix @register_wrap class AvatarToolkit_OT_StartPoseMode(Operator): @@ -95,98 +97,10 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator): return get_selected_armature(context) != None and context.mode == "POSE" def execute(self, context: Context): - for obj in get_all_meshes(context): - mesh_data: Mesh = obj.data - - - - if mesh_data.shape_keys: - shape_key_obj_list: list[bpy.types.Object] = [] - modifier_armature_name: str = "" - - for modifier in obj.modifiers: - if modifier.type == "ARMATURE": - arm_modifier: bpy.types.ArmatureModifier = modifier - modifier_armature_name = arm_modifier.object.name - for idx,shape in enumerate(mesh_data.shape_keys.key_blocks): - if idx == 0: - continue - context.view_layer.objects.active = obj - - bpy.ops.object.mode_set(mode="OBJECT") - bpy.ops.object.select_all(action="DESELECT") - context.view_layer.objects.active = obj - obj.select_set(True) - - #create duplicate of object - bpy.ops.object.duplicate() - - shape_obj = context.view_layer.objects.active - - #make current shapekey a separate object - shape_obj.active_shape_key_index = idx - shape_obj.name = shape.name - - bpy.ops.object.shape_key_move(type="TOP") - - bpy.ops.object.mode_set(mode="EDIT") - bpy.ops.object.mode_set(mode="OBJECT") - - bpy.ops.object.shape_key_remove(all=True) - - bpy.ops.object.modifier_apply(modifier=modifier_armature_name) - - #for modifier_name in [i.name for i in shape_obj.modifiers]: - # bpy.ops.object.modifier_remove(modifier=modifier_name) - - shape_key_obj_list.append(shape_obj) #add to a list of shape key objects - context.view_layer.objects.active = obj - - bpy.ops.object.mode_set(mode="OBJECT") - context.view_layer.objects.active.select_set(True) - bpy.ops.object.shape_key_remove(all=True) - bpy.ops.object.modifier_apply(modifier=modifier_armature_name) - bpy.ops.object.select_all(action="DESELECT") - - for shapekey_obj in shape_key_obj_list: - shapekey_obj.select_set(True) - context.view_layer.objects.active = obj - context.view_layer.objects.active.select_set(True) - - try: - bpy.ops.object.join_shapes() - except: - self.report({'ERROR'}, t("Quick_Access.apply_armature_failed")) - #delete shapekey objects to not leave ourselves in a bad exit state - @989onan - context.view_layer.objects.active = shape_key_obj_list[0] - obj.select_set(False) - bpy.ops.object.delete(confirm=False) - return {'CANCELLED'} - context.view_layer.objects.active = shape_key_obj_list[0] - obj.select_set(False) - bpy.ops.object.delete(confirm=False) - else: - modifier_armature_name: str = "" - - for modifier in obj.modifiers: - if modifier.type == "ARMATURE": - arm_modifier: bpy.types.ArmatureModifier = modifier - modifier_armature_name = arm_modifier.object.name - context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode="OBJECT") - bpy.ops.object.select_all(action="DESELECT") - context.view_layer.objects.active.select_set(True) - bpy.ops.object.modifier_apply(modifier=modifier_armature_name) - - armature_obj: bpy.types.Object = get_selected_armature(context) - - context.view_layer.objects.active = armature_obj - armature_obj.select_set(True) - bpy.ops.object.mode_set(mode="OBJECT") - bpy.ops.object.mode_set(mode="POSE") - - bpy.ops.pose.armature_apply(selected=False) - + + if common.apply_pose_as_rest(armature_obj=get_selected_armature(context),meshes=get_all_meshes(context), context=context): + self.report({'ERROR'}, t("Quick_Access.apply_armature_failed")) + return {'FINISHED'} return {'FINISHED'} @register_wrap @@ -349,8 +263,6 @@ class AvatarToolkit_OT_MergeArmatures(Operator): bl_description = t("MergeArmature.merge_armatures.desc").format(selected_armature_label=t("MergeArmatures.selected_armature.label")) bl_options = {'REGISTER', 'UNDO'} - """align_bones: bpy.props.BoolProperty(default=False,name=t("MergeArmature.merge_armatures.align_bones.label"),description=t("MergeArmature.merge_armatures.align_bones.desc"))""" - @classmethod def poll(cls, context: Context) -> bool: return (common.get_selected_armature(context) is not None) and (context.scene.merge_armature_source is not None) @@ -372,22 +284,54 @@ class AvatarToolkit_OT_MergeArmatures(Operator): cls.make_active(obj=source_armature, context=context) - #TODO: This is woefully screwed. This needs to be fixed - @989onan - """if cls.align_bones: - bpy.ops.object.mode_set(mode='POSE') - for bone in source_armature.pose.bones: - if bone.name in target_armature_data.bones: - #sorry for this one liner - @989onan - bone.matrix = source_armature.convert_space(matrix=target_armature.convert_space(matrix=target_armature_data.bones[bone.name].matrix_local, pose_bone=None,from_space='LOCAL',to_space='WORLD'), pose_bone=None, from_space='WORLD', to_space='LOCAL') - - if not common.apply_pose_as_rest(armature=source_armature,meshes=[i for i in source_armature.children if i.type == 'MESH'], context=context): + if context.scene.merge_armature_apply_transforms: + target_armature.select_set(True) + for obj in target_armature.children: + obj.select_set(True) + for obj in source_armature.children: + obj.select_set(True) + bpy.ops.object.transform_apply() + + + if context.scene.merge_armature_align_bones: + if not context.scene.merge_armature_apply_transforms: + source_armature.matrix_world = target_armature.matrix_world + + def children_bone_recursive(parent_bone) -> list[bpy.types.PoseBone]: + child_bones = [] + child_bones.append(parent_bone) + for child in parent_bone.children: + child_bones.extend(children_bone_recursive(child)) + return child_bones + bpy.ops.object.mode_set(mode='POSE') + source_armature_bone_names = [j.name for j in children_bone_recursive( + source_armature.pose.bones[ + next(bone.name for bone in source_armature.pose.bones if common.simplify_bonename(bone.name) in bone_names['hips']) #Find bone that matches dictionary for hips before continuing. + ] + )] #bones are default in order of parent child. + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + context.view_layer.objects.active = target_armature + bpy.ops.object.mode_set(mode='EDIT') + for source_bone_name in source_armature_bone_names: + + if source_bone_name in target_armature_data.edit_bones: + obj = source_armature + editbone = target_armature_data.edit_bones[source_bone_name] + bone = obj.pose.bones[source_bone_name] + bone.matrix = editbone.matrix + else: + continue + if not common.apply_pose_as_rest(armature_obj=source_armature,meshes=[i for i in source_armature.children if i.type == 'MESH'], context=context): cls.report({'ERROR'}, t("Quick_Access.apply_armature_failed")) - return {'FINISHED'}""" + return {'FINISHED'} + cls.make_active(obj=source_armature, context=context) bpy.ops.object.mode_set(mode='EDIT') source_armature_data: Armature = source_armature.data diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index a567c7d..40e635e 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -169,10 +169,14 @@ "MergeArmatures.label": "Merge Armatures", "MergeArmatures.selected_armature.label": "Armature to Merge From", "MergeArmatures.selected_armature.desc": "The armature that should be merged into the targeted armature for Avatar Toolkit.", + "MergeArmatures.target_armature.label": "Armature to Merge To", + "MergeArmatures.target_armature.desc": "The armature that should be the target for merging armatures.", "MergeArmature.merge_armatures.label": "Merge Armatures Together", "MergeArmature.merge_armatures.desc": "Merge {selected_armature_label} to the targeted armature for Avatar Toolkit.", "MergeArmature.merge_armatures.align_bones.label": "Align Bones", "MergeArmature.merge_armatures.align_bones.desc": "Align bones from source armature to target armature,\nstretching bones to match before merging.", + "MergeArmature.merge_armatures.apply_transforms.label": "Apply Transforms", + "MergeArmature.merge_armatures.apply_transforms.desc": "Apply transforms on armature and it's meshes before merging.", "VisemePanel.create_visemes": "Create Visemes", "VisemePanel.creating_viseme": "Creating viseme: {viseme_name}", "VisemePanel.creating_viseme_detail": "Creating viseme: {viseme_name}", diff --git a/ui/merge_armatures.py b/ui/merge_armatures.py index e2ea43c..8df0549 100644 --- a/ui/merge_armatures.py +++ b/ui/merge_armatures.py @@ -23,8 +23,18 @@ class AvatarToolkit_PT_MergeArmaturesPanel(Panel): if armature: layout.label(text=t("MergeArmatures.title.label"), icon='ARMATURE_DATA') + layout.separator(factor=0.5) - layout.prop(context.scene,property="merge_armature_source",icon="ARMATURE_DATA") - layout.operator(operator=AvatarToolkit_OT_MergeArmatures.bl_idname,icon="ARMATURE_DATA") + row = layout.row(align=True) + row.prop(context.scene, property="selected_armature",text=t("MergeArmatures.target_armature.label"),icon="STYLUS_PRESSURE") + row = layout.row(align=True) + row.prop(context.scene, property="merge_armature_source",icon="SORT_DESC") + row = layout.row(align=True) + row.prop(context.scene, property="merge_armature_align_bones") + row = layout.row(align=True) + row.prop(context.scene, property="merge_armature_apply_transforms") + row = layout.row(align=True) + row.operator(operator=AvatarToolkit_OT_MergeArmatures.bl_idname,icon="ARMATURE_DATA") + else: layout.label(text=t("MergeArmatures.select_armature"), icon='ERROR') \ No newline at end of file