From 9ec186b1cfc8791473b52947eb7f4dda473a83e7 Mon Sep 17 00:00:00 2001 From: 989onan Date: Sun, 7 Jul 2024 16:19:52 -0400 Subject: [PATCH 1/3] Added a modal for remove doubles - made remove doubles a blender modal. this way the code can run over multiple frames. - Since remove doubles is async now, the user gets feedback on which shapekey and mesh is being worked on - this does not remove doubles correctly yet, but is very close to ready --- .gitignore | 1 + .vscode/settings.json | 11 +++ functions/remove_doubles_safely.py | 103 +++++++++++++++++++++++++++++ ui/optimization.py | 1 + ui/tools.py | 4 +- 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 functions/remove_doubles_safely.py diff --git a/.gitignore b/.gitignore index fd20fdd..5aea8c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc +.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json index e69de29..96df99e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.analysis.extraPaths": [ + "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.0\\scripts\\addons", + "C:\\Users\\Onan\\AppData\\Roaming\\Blender Foundation\\Blender\\4.0\\scripts\\addons",//C:/Users/Onan/AppData/Roaming/Blender Foundation/Blender/4.0/scripts/addons + "D:\\blender stuff\\blendercodestuff\\4.0" + ], + "python.analysis.diagnosticSeverityOverrides": { + "reportInvalidTypeForm": "none" + }, + "python.REPL.enableREPLSmartSend": false, +} \ No newline at end of file diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py new file mode 100644 index 0000000..e962df6 --- /dev/null +++ b/functions/remove_doubles_safely.py @@ -0,0 +1,103 @@ +from ast import Dict +from itertools import count +import bpy +import re +from typing import List, Tuple, Optional, TypedDict +from bpy.types import Material, Operator, Context, Object +from ..core.register import register_wrap + + +class meshEntry(TypedDict): + mesh: bpy.types.Object + shapekeys: list[str] + +@register_wrap +class RemoveDoublesSafely(Operator): + bl_idname = "avatar_toolkit.remove_doubles_safely" + bl_label = "Remove Doubles Safely" + bl_description = "Remove Doubles on all meshes, making sure to not fuse things like mouths together." + bl_options = {'REGISTER', 'UNDO'} + objects_to_do: list[meshEntry] = [] + + @classmethod + def poll(cls, context: Context) -> bool: + return context.mode == 'OBJECT' + + def execute(self, context: Context) -> set: + if not bpy.data.objects: + self.report({'INFO'}, "No objects in the scene") + return + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + + meshes: List[Object] = [obj for obj in context.view_layer.objects if obj.type == 'MESH'] + + for mesh in meshes: + if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]: + mesh_shapekeys = {"mesh":mesh,"shapekeys":[]} + mesh_data: bpy.types.Mesh = mesh.data + shape: bpy.types.ShapeKey = None + if mesh_data.shape_keys: + for shape in mesh_data.shape_keys.key_blocks: + mesh_shapekeys["shapekeys"].append(shape.name) + self.objects_to_do.append(mesh_shapekeys) + + + return {'FINISHED'} + + def invoke(self, context: Context, event: bpy.types.Event) -> set: + + self.execute(context) + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def modify_mesh(self, context: Context, mesh: meshEntry): + mesh["mesh"].select_set(True) + context.view_layer.objects.active = mesh["mesh"] + bpy.ops.object.mode_set(mode='EDIT') + + + + + + + + + bpy.ops.object.mode_set(mode='OBJECT') + mesh["mesh"].select_set(False) + + + def modal(self, context: Context, event: bpy.types.Event) -> set: + + + + + if len(self.objects_to_do) > 0: + mesh = self.objects_to_do[0] + mesh_data: bpy.types.Mesh = mesh["mesh"].data + if len(mesh['shapekeys']) > 0: + shapekeyname: str = mesh['shapekeys'].pop(0) + + target_shapekey: int = mesh_data.shape_keys.key_blocks.find(shapekeyname) + mesh["mesh"].active_shape_key_index = target_shapekey + print("doing shapekey \""+shapekeyname+"\" on mesh \""+mesh['mesh'].name+"\".") + self.modify_mesh(context, mesh) + + elif not (mesh_data.shape_keys): + print("doing mesh with no shapekeys named \""+mesh['mesh'].name+"\".") + self.modify_mesh(context, mesh) + self.objects_to_do.pop(0) + else: + self.objects_to_do.pop(0) + else: + return {'FINISHED'} + + return {'RUNNING_MODAL'} + + + + + + + diff --git a/ui/optimization.py b/ui/optimization.py index 2f6e69a..0de9bae 100644 --- a/ui/optimization.py +++ b/ui/optimization.py @@ -25,6 +25,7 @@ class AvatarToolkitOptimizationPanel(bpy.types.Panel): row.scale_y = 1.2 row.operator("avatar_toolkit.join_all_meshes", text="Join All Meshes") row.operator("avatar_toolkit.join_selected_meshes", text="Join Selected Meshes") + row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely") # Add optimization options here diff --git a/ui/tools.py b/ui/tools.py index 408a95d..2c2ea05 100644 --- a/ui/tools.py +++ b/ui/tools.py @@ -19,4 +19,6 @@ class AvatarToolkitToolsPanel(bpy.types.Panel): row = layout.row(align=True) row.scale_y = 1.5 - row.operator("avatar_toolkit.convert_to_resonite", text="Translate to Resonite") \ No newline at end of file + row.operator("avatar_toolkit.convert_to_resonite", text="Translate to Resonite") + row = layout.row(align=True) + row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely") \ No newline at end of file From 12e651f68c974f3dc96163820719270fa0fd872b Mon Sep 17 00:00:00 2001 From: 989onan Date: Sun, 7 Jul 2024 17:49:03 -0400 Subject: [PATCH 2/3] Finished merge doubles this now does doubles asyncronously --- .vscode/settings.json | 6 ++--- functions/remove_doubles_safely.py | 41 +++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 96df99e..e685f8d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,8 @@ { "python.analysis.extraPaths": [ - "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.0\\scripts\\addons", - "C:\\Users\\Onan\\AppData\\Roaming\\Blender Foundation\\Blender\\4.0\\scripts\\addons",//C:/Users/Onan/AppData/Roaming/Blender Foundation/Blender/4.0/scripts/addons - "D:\\blender stuff\\blendercodestuff\\4.0" + "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\scripts\\addons", + "C:\\Users\\Onan\\AppData\\Roaming\\Blender Foundation\\Blender\\4.3\\extensions\\user_default\\",//C:/Users/Onan/AppData/Roaming/Blender Foundation/Blender/4.0/scripts/addons + "D:\\blender stuff\\blendercodestuff\\4.3" ], "python.analysis.diagnosticSeverityOverrides": { "reportInvalidTypeForm": "none" diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py index e962df6..1263e2d 100644 --- a/functions/remove_doubles_safely.py +++ b/functions/remove_doubles_safely.py @@ -18,6 +18,7 @@ class RemoveDoublesSafely(Operator): bl_description = "Remove Doubles on all meshes, making sure to not fuse things like mouths together." bl_options = {'REGISTER', 'UNDO'} objects_to_do: list[meshEntry] = [] + merge_distance: bpy.props.FloatProperty(default=0.0001) @classmethod def poll(cls, context: Context) -> bool: @@ -55,11 +56,17 @@ class RemoveDoublesSafely(Operator): def modify_mesh(self, context: Context, mesh: meshEntry): mesh["mesh"].select_set(True) context.view_layer.objects.active = mesh["mesh"] + context.view_layer.objects.active = mesh["mesh"] + mesh_data: bpy.types.Mesh = mesh["mesh"].data bpy.ops.object.mode_set(mode='EDIT') - - + bpy.ops.object.mode_set(mode='OBJECT') + for index, point in enumerate(mesh["mesh"].active_shape_key.points): + if point.co.xyz != mesh_data.shape_keys.key_blocks[0].points[index].co.xyz: + mesh_data.vertices[index].select = True + print("shapekey has a moved vertex at index \""+str(index)+"\", excluding from double merging!") + bpy.ops.object.mode_set(mode='EDIT') @@ -86,10 +93,38 @@ class RemoveDoublesSafely(Operator): elif not (mesh_data.shape_keys): print("doing mesh with no shapekeys named \""+mesh['mesh'].name+"\".") - self.modify_mesh(context, mesh) + mesh["mesh"].select_set(True) + context.view_layer.objects.active = mesh["mesh"] + bpy.ops.object.mode_set(mode='EDIT') + mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) + + bpy.ops.mesh.select_all(action="INVERT") + bpy.ops.mesh.remove_doubles(threshold=self.merge_distance,use_unselected=False) + + bpy.ops.object.mode_set(mode='OBJECT') + mesh["mesh"].select_set(False) self.objects_to_do.pop(0) else: + mesh["mesh"].select_set(True) + context.view_layer.objects.active = mesh["mesh"] + bpy.ops.object.mode_set(mode='EDIT') + + bpy.ops.mesh.select_all(action="INVERT") + bpy.ops.mesh.remove_doubles(threshold=self.merge_distance,use_unselected=False) + + bpy.ops.object.mode_set(mode='OBJECT') + mesh["mesh"].select_set(False) + self.objects_to_do.pop(0) + if len(self.objects_to_do) > 0: + mesh = self.objects_to_do[0] + mesh["mesh"].select_set(True) + context.view_layer.objects.active = mesh["mesh"] + bpy.ops.object.mode_set(mode='EDIT') + mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) + bpy.ops.object.mode_set(mode='OBJECT') + mesh["mesh"].select_set(False) + else: return {'FINISHED'} From 88061d2ad5f4176f6864609c8bfab07f0b0c6183 Mon Sep 17 00:00:00 2001 From: 989onan Date: Sun, 7 Jul 2024 18:42:57 -0400 Subject: [PATCH 3/3] Update remove_doubles_safely.py use the get armature method properly --- functions/remove_doubles_safely.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py index 1263e2d..99850ae 100644 --- a/functions/remove_doubles_safely.py +++ b/functions/remove_doubles_safely.py @@ -5,6 +5,7 @@ import re from typing import List, Tuple, Optional, TypedDict from bpy.types import Material, Operator, Context, Object from ..core.register import register_wrap +from ..core.common import get_armature class meshEntry(TypedDict): @@ -31,8 +32,9 @@ class RemoveDoublesSafely(Operator): bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') + objects: List[Object] = get_armature(context).children if get_armature(context) else context.view_layer.objects - meshes: List[Object] = [obj for obj in context.view_layer.objects if obj.type == 'MESH'] + meshes: List[Object] = [obj for obj in objects if obj.type == 'MESH'] for mesh in meshes: if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]: