From 0cb4d6bb3a378c00f7d8713a6e2213af59624884 Mon Sep 17 00:00:00 2001 From: 989onan Date: Thu, 12 Sep 2024 21:56:39 -0400 Subject: [PATCH 1/2] Merge armatures button --- core/properties.py | 12 +++-- functions/armature_modifying.py | 74 ++++++++++++++++++++++++++++++- resources/translations/en_US.json | 11 +++++ ui/atlas_materials.py | 2 +- ui/merge_armatures.py | 30 +++++++++++++ ui/settings.py | 2 +- ui/viseme.py | 2 +- 7 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 ui/merge_armatures.py diff --git a/core/properties.py b/core/properties.py index 45a814f..4e0e69a 100644 --- a/core/properties.py +++ b/core/properties.py @@ -21,6 +21,12 @@ def register() -> None: name=t("VisemePanel.selected_mesh.label"), description=t("VisemePanel.selected_mesh.desc") ))) + + register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty( + items=get_armatures, + name=t("MergeArmatures.selected_armature.label"), + description=t("MergeArmatures.selected_armature.label") + ))) register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False))) @@ -49,8 +55,8 @@ def register() -> None: register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty( items=get_armatures, - name="Selected Armature", - description="The currently selected armature for Avatar Toolkit operations" + name=t("Quick_Access.selected_armature.label"), + description=t("Quick_Access.selected_armature.desc") ))) #happy with how compressed this get_texture_node_list method is - @989onan @@ -88,7 +94,7 @@ def register() -> None: items=get_texture_node_list))) register_property((Material, "texture_atlas_height", EnumProperty( name=t("TextureAtlas.height"), - description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height_map").lower()), + description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height").lower()), default=0, items=get_texture_node_list))) register_property((Material, "texture_atlas_roughness", EnumProperty( diff --git a/functions/armature_modifying.py b/functions/armature_modifying.py index c1ad4bb..b77e67f 100644 --- a/functions/armature_modifying.py +++ b/functions/armature_modifying.py @@ -338,6 +338,78 @@ class AvatarToolkit_OT_MergeBonesToParents(Operator): bone_child.parent = armature_data.edit_bones[bone].parent armature_data.edit_bones.remove(armature_data.edit_bones[bone]) - bpy.ops.object.mode_set(mode=prev_mode) return {'FINISHED'} + + +@register_wrap +class AvatarToolkit_OT_MergeArmatures(Operator): + bl_idname = "avatar_toolkit.merge_armatures" + bl_label = t("MergeArmature.merge_armatures.label") + 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) + + def make_active(self, obj: bpy.types.Object, context: Context): + 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) + + def execute(cls, context: Context) -> set[str]: + source_armature: bpy.types.Object = bpy.data.objects[context.scene.merge_armature_source] + source_armature_data: Armature = source_armature.data + target_armature: bpy.types.Object = common.get_selected_armature(context) + target_armature_data: Armature = target_armature.data + parent_dictionary: dict[str, list[str]] = {} + + 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): + cls.report({'ERROR'}, t("Quick_Access.apply_armature_failed")) + return {'FINISHED'}""" + + + + + cls.make_active(obj=source_armature, context=context) + bpy.ops.object.mode_set(mode='EDIT') + source_armature_data: Armature = source_armature.data + for bone_name in [i.name for i in source_armature_data.edit_bones]: + if bone_name in target_armature_data.bones: + parent_dictionary[bone_name] = [i.name for i in source_armature_data.edit_bones[bone_name].children] + source_armature_data.edit_bones.remove(source_armature_data.edit_bones[bone_name]) + bpy.ops.object.mode_set(mode='OBJECT') + + cls.make_active(obj=target_armature, context=context) + source_armature.select_set(True) + + bpy.ops.object.join() + target_armature: bpy.types.Object = common.get_selected_armature(context) + cls.make_active(obj=target_armature, context=context) + bpy.ops.object.mode_set(mode='EDIT') + for bone_name, bone_name_list in parent_dictionary.items(): + if bone_name in target_armature_data.edit_bones: + for bone_child in bone_name_list: + target_armature_data.edit_bones[bone_child].parent = target_armature_data.edit_bones[bone_name] + bpy.ops.object.mode_set(mode='OBJECT') + + + + return {'FINISHED'} diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index 7358d95..a567c7d 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -62,6 +62,8 @@ "Optimization.selecting_meshes": "Selecting meshes...", "Optimization.transform_apply_failed": "Transform apply failed", "Optimization.vertex_excluded": "Shapekey has a moved vertex at index \"{index}\", excluding from double merging!", + "Quick_Access.selected_armature.label": "Selected Armature", + "Quick_Access.selected_armature.desc": "The currently \"targeted\" armature for Avatar Toolkit operations", "Quick_Access.export": "Export", "Quick_Access.export_fbx.desc": "Export the model as FBX", "Quick_Access.export_fbx.label": "Export FBX", @@ -162,6 +164,15 @@ "Tools.merge_bones_to_parents.label": "Merge Bones to Individual Parents", "Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold", "Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed", + "MergeArmatures.select_armature": "Please select an armature", + "MergeArmatures.title.label": "Merge Armatures:", + "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.", + "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.", "VisemePanel.create_visemes": "Create Visemes", "VisemePanel.creating_viseme": "Creating viseme: {viseme_name}", "VisemePanel.creating_viseme_detail": "Creating viseme: {viseme_name}", diff --git a/ui/atlas_materials.py b/ui/atlas_materials.py index 09a7d1f..e5e1577 100644 --- a/ui/atlas_materials.py +++ b/ui/atlas_materials.py @@ -66,7 +66,7 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel): bl_region_type = 'UI' bl_category = CATEGORY_NAME bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname - bl_order = 4 + bl_order = 5 def draw(self, context: Context): layout = self.layout diff --git a/ui/merge_armatures.py b/ui/merge_armatures.py new file mode 100644 index 0000000..e2ea43c --- /dev/null +++ b/ui/merge_armatures.py @@ -0,0 +1,30 @@ + +import bpy +from ..core.register import register_wrap +from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from bpy.types import Panel, Context +from ..core.common import get_selected_armature +from ..functions.translations import t +from ..functions.armature_modifying import AvatarToolkit_OT_MergeArmatures + +@register_wrap +class AvatarToolkit_PT_MergeArmaturesPanel(Panel): + bl_label = t("MergeArmatures.label") + bl_idname = "OBJECT_PT_avatar_toolkit_merge_armatures" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = CATEGORY_NAME + bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname + bl_order = 4 + + def draw(self, context: Context): + layout = self.layout + armature = get_selected_armature(context) + + 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") + else: + layout.label(text=t("MergeArmatures.select_armature"), icon='ERROR') \ No newline at end of file diff --git a/ui/settings.py b/ui/settings.py index b88d514..dfe2393 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -11,7 +11,7 @@ class AvatarToolkitSettingsPanel(bpy.types.Panel): bl_region_type = 'UI' bl_category = CATEGORY_NAME bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname - bl_order = 6 + bl_order = 7 def draw(self, context): layout = self.layout diff --git a/ui/viseme.py b/ui/viseme.py index 8c6fc3d..ac39b05 100644 --- a/ui/viseme.py +++ b/ui/viseme.py @@ -13,7 +13,7 @@ class AvatarToolkitVisemePanel(bpy.types.Panel): bl_region_type = 'UI' bl_category = CATEGORY_NAME bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname - bl_order = 5 + bl_order = 6 def draw(self, context: bpy.types.Context) -> None: layout = self.layout From b015736eadee6af1bd7ba365191bf38e543b2719 Mon Sep 17 00:00:00 2001 From: 989onan Date: Fri, 13 Sep 2024 12:38:08 -0400 Subject: [PATCH 2/2] 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