diff --git a/__init__.py b/__init__.py index 8817dec..eea85d4 100644 --- a/__init__.py +++ b/__init__.py @@ -33,6 +33,11 @@ def register(): #finally register properties that may use some classes. core.register.register_properties() + from functions.mesh_tools import AvatarToolkit_OT_ApplyShapeKey + + bpy.types.MESH_MT_shape_key_context_menu.append((lambda self, context: self.layout.separator())) + bpy.types.MESH_MT_shape_key_context_menu.append((lambda self, context: self.layout.operator(AvatarToolkit_OT_ApplyShapeKey.bl_idname, icon="KEY_HLT"))) + def unregister(): print("Unregistering Avatar Toolkit") # Unregister the UI classes diff --git a/core/common.py b/core/common.py index c4e06fa..6b060f9 100644 --- a/core/common.py +++ b/core/common.py @@ -144,6 +144,36 @@ def select_current_armature(context: Context) -> bool: return True return False +def apply_shapekey_to_basis(context: bpy.types.Context, obj: bpy.types.Object, shape_key_name: str, delete_old: bool = False) -> bool: + if shape_key_name not in obj.data.shape_keys.key_blocks: + return False + shapekeynum = obj.data.shape_keys.key_blocks.find(shape_key_name) + + bpy.ops.object.mode_set(mode="EDIT") + + bpy.ops.mesh.select_all(action='SELECT') + + + obj.active_shape_key_index = 0 + bpy.ops.mesh.blend_from_shape(shape = shape_key_name, add=True, blend=1) + obj.active_shape_key_index = shapekeynum + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.blend_from_shape(shape = shape_key_name, add=True, blend=-2) + + + bpy.ops.mesh.select_all(action='DESELECT') + + bpy.ops.object.mode_set(mode="OBJECT") + print("blended!") + + if delete_old: + obj.active_shape_key_index = shapekeynum + bpy.ops.object.shape_key_remove(all=False) + else: + mesh: bpy.types.Mesh = obj.data + mesh.shape_keys.key_blocks[shape_key_name].name = shape_key_name + "_reversed" + return True + 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 diff --git a/functions/mesh_tools.py b/functions/mesh_tools.py index 9819e66..89a76ac 100644 --- a/functions/mesh_tools.py +++ b/functions/mesh_tools.py @@ -1,7 +1,7 @@ import numpy as np import bpy from bpy.types import Context -from ..core.common import get_selected_armature, get_all_meshes, is_valid_armature +from ..core.common import get_selected_armature, get_all_meshes, is_valid_armature, apply_shapekey_to_basis, has_shapekeys from ..functions.translations import t from ..core.register import register_wrap @@ -53,4 +53,29 @@ class AvatarToolkit_OT_RemoveUnusedShapekeys(bpy.types.Operator): for kb_name in to_delete: if ("-" in kb_name) or ("=" in kb_name) or ("~" in kb_name): #don't delete category names. - @989onan continue - ob.shape_key_remove(ob.data.shape_keys.key_blocks[kb_name]) \ No newline at end of file + ob.shape_key_remove(ob.data.shape_keys.key_blocks[kb_name]) + + + +@register_wrap +class AvatarToolkit_OT_ApplyShapeKey(bpy.types.Operator): + bl_idname = "avatar_toolkit.apply_shape_key" + bl_label = t("Tools.apply_shape_key.label") + bl_description = t("Tools.apply_shape_key.desc") + bl_options = {'REGISTER', 'UNDO'} + + + @classmethod + def poll(cls, context: Context) -> bool: + armature = get_selected_armature(context) + return armature is not None and is_valid_armature(armature) and (len(get_all_meshes(context)) > 0) and (context.mode == "OBJECT") and context.view_layer.objects.active is not None and has_shapekeys(context.view_layer.objects.active) + + def execute(self, context: Context) -> set[str]: + obj: bpy.types.Object = context.view_layer.objects.active + + + if (apply_shapekey_to_basis(context,obj,obj.active_shape_key.name,False)): + return {'FINISHED'} + else: + self.report({'ERROR'}, t("Tools.apply_shape_key.error")) + return {'FINISHED'} \ No newline at end of file diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index 0946093..6f01c2b 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -152,6 +152,9 @@ "Tools.remove_unused_shapekeys.tolerance.desc": "Min movement for position on any coordinate\n for any vertex for a shapekey to be kept.", "Tools.remove_unused_shapekeys.desc": "Remove shapekeys that don't move anything.\nDoesn't get rid of category shapekeys.\n(ex: has \"~\", \"-\", or \"=\" in the name.)", "Tools.remove_unused_shapekeys.tolerance.label": "Position Tolerance", + "Tools.apply_shape_key.label": "Apply Shapekey to Basis", + "Tools.apply_shape_key.desc": "Apply the selected shapekey to the basis, making it default on.", + "Tools.apply_shape_key.error": "The shape keys were not merged for some reason!", "Tools.remove_zero_weight_bones.success": "Zero weight bones removed successfully", "Tools.remove_zero_weight_bones.label": "Remove Zero Weight Bones", "Tools.remove_zero_weight_bones.desc": "Remove bones from the armature that have weights less than threshold.",