From eff1d9efe2f1bb026b3e9e84f024cd1ceeab6703 Mon Sep 17 00:00:00 2001 From: Onan Chew Date: Sat, 31 Aug 2024 15:29:12 -0400 Subject: [PATCH 1/2] start of advanced mode almost done, just need to find out why doubles aren't merging even when perfectly together when using merge to unselected --- .vscode/settings.json | 4 +- functions/remove_doubles_safely.py | 187 +++++++++++++++++++++++++---- ui/tools.py | 2 +- 3 files changed, 170 insertions(+), 23 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e685f8d..2021d12 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,9 @@ "python.analysis.extraPaths": [ "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" + "D:\\blender stuff\\blendercodestuff\\4.3", + "/Users/frankche/Documents/blendercoding/4.1/", + "/Users/frankche/Library/Application Support/Blender/4.3/extensions/user_default/" ], "python.analysis.diagnosticSeverityOverrides": { "reportInvalidTypeForm": "none" diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py index 9c2078c..d00f71a 100644 --- a/functions/remove_doubles_safely.py +++ b/functions/remove_doubles_safely.py @@ -2,15 +2,18 @@ from ast import Dict from itertools import count import bpy import re -from typing import List, Tuple, Optional, TypedDict +from typing import List, Tuple, Optional, TypedDict, Any from bpy.types import Material, Operator, Context, Object from ..core.register import register_wrap from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes from ..functions.translations import t class meshEntry(TypedDict): - mesh: bpy.types.Object + mesh: Object shapekeys: list[str] + vertices: int + cur_vertex_pass: int + mesh_shapekeys: dict[str, Object] @register_wrap class RemoveDoublesSafely(Operator): @@ -20,6 +23,7 @@ class RemoveDoublesSafely(Operator): bl_options = {'REGISTER', 'UNDO'} objects_to_do: list[meshEntry] = [] merge_distance: bpy.props.FloatProperty(default=0.0001) + advanced: bpy.props.BoolProperty(default=False) @classmethod def poll(cls, context: Context) -> bool: @@ -35,20 +39,49 @@ class RemoveDoublesSafely(Operator): bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') objects: List[Object] = get_all_meshes(context) + self.objects_to_do = [] for mesh in objects: if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]: - mesh_shapekeys = {"mesh":mesh,"shapekeys":[]} + print("setting up data for object" + mesh.name) + mesh_shapekeys = {"mesh":mesh,"shapekeys":[],"vertices":0,"cur_vertex_pass":0,"mesh_shapekeys":{}} mesh_data: bpy.types.Mesh = mesh.data shape: bpy.types.ShapeKey = None + mesh_shapekeys["vertices"] = len(mesh_data.vertices) + 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') + if mesh_data.shape_keys: for shape in mesh_data.shape_keys.key_blocks: mesh_shapekeys["shapekeys"].append(shape.name) + if self.advanced: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + context.view_layer.objects.active = mesh + mesh.select_set(True) + mesh.active_shape_key_index = mesh_data.shape_keys.key_blocks.find(shape.name) + bpy.ops.object.duplicate() + newobj = context.view_layer.objects.active + 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, apply_mix=False) + newobj.name = shape.name+"_object_is_"+mesh.name + mesh_shapekeys["mesh_shapekeys"][shape.name] = newobj + + context.view_layer.objects.active = mesh + bpy.ops.object.select_all(action='DESELECT') + print("queued data for "+mesh.name+" is: ") + print(mesh_shapekeys) self.objects_to_do.append(mesh_shapekeys) return {'FINISHED'} def invoke(self, context: Context, event: bpy.types.Event) -> set: + print("starting modal execution of merge doubles safely.") self.execute(context) context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -56,7 +89,6 @@ 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') @@ -69,19 +101,120 @@ class RemoveDoublesSafely(Operator): bpy.ops.object.mode_set(mode='OBJECT') mesh["mesh"].select_set(False) + print("finished shapekey basic.") + + def modify_mesh_advanced(self, context: Context, mesh_entry: meshEntry): + + final_merged_vertex_group: list[int] = [] + + + for shapekey_name in mesh_entry["shapekeys"]: + mesh = mesh_entry["mesh_shapekeys"][shapekey_name] + + + + #make a copy to do double merge testing on for the current vertex + context.view_layer.objects.active = mesh + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + context.view_layer.objects.active = mesh + mesh_data: bpy.types.Mesh = mesh.data + vertices_original: dict[int,Any] = {} + original_count: int = len(mesh_data.vertices) + mesh.select_set(True) + bpy.ops.object.duplicate() + mesh = context.view_layer.objects.active + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + + mesh.select_set(True) + context.view_layer.objects.active = mesh + mesh_data: bpy.types.Mesh = mesh.data + bpy.ops.object.mode_set(mode='EDIT') + + + + bpy.ops.object.mode_set(mode='OBJECT') + for index, merged_point in enumerate(mesh_data.vertices): + vertices_original[index] = merged_point.co.xyz + + + #if point.co.xyz != original_mesh_data.shape_keys.key_blocks[0].points[index].co.xyz: + # mesh_data.vertices[index].select = True + # break + print("vertex indices and their positions.") + print(vertices_original) + print("vertex positions end.") + 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') + + select_target_vertex = [False]*len(mesh_data.vertices) + try: + select_target_vertex[mesh_entry["cur_vertex_pass"]] = True + except: + bpy.ops.object.delete() #remove our double merge testing object for this shapekey, since we merged doubles on it, it will be useless. + return True + print("vertex select list:") + print(select_target_vertex) + + + bpy.ops.object.mode_set(mode='EDIT') + mesh_data.vertices.foreach_set("select",select_target_vertex) + bpy.ops.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=True, use_sharp_edge_from_normals=False) + bpy.ops.object.mode_set(mode='OBJECT') + #this doesn't keep in mind vertices that are exactly in the same place in the shapekey positional wise that also appear right after one another in the array + #Based on my internal thoughts and theories, this will cause problems. I don't know the solution to this, so it's fine for now. + # besides, the chance of this happening should be very very slim, and will require user input to fix. - @989onan + # { + # "1":"0,0,0" + # "2":"0,0,0" + # "3":"1,0,0" + # } + + merged_vertices: list[int] = [] + + for i in range(0,original_count): + if mesh_data.vertices[i+len(merged_vertices)].co.xyz != vertices_original[i]: + merged_vertices.append(i) + + #iterate through a copy of final vertex groups to prevent crash. If a vertex was merged before, but didn't merge in this vertex, + # then the vertex shouldn't be merged because it moves away from the vertex we are double merging now (ex: bottom of mouth moving away from top when opening on a shapekey) - @989onan + for merged_point in final_merged_vertex_group[:]: + if merged_point not in merged_vertices: + final_merged_vertex_group.remove(merged_point) + else: + final_merged_vertex_group.append(merged_point) + + 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') + bpy.ops.object.delete() #remove our double merge testing object for this shapekey, since we merged doubles on it, it will be useless. + context.view_layer.objects.active = mesh_entry["mesh"] + bpy.ops.object.mode_set(mode='EDIT') + original_mesh_data: bpy.types.Mesh = mesh_entry["mesh"].data + select_target_group = [False]*len(mesh_data.vertices) + for vertex_index in final_merged_vertex_group: + select_target_group[vertex_index] = True + original_mesh_data.vertices.foreach_set("select",select_target_group) + bpy.ops.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=False, use_sharp_edge_from_normals=False) + mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) + bpy.ops.object.mode_set(mode='OBJECT') + print("finished shapekey advanced.") + return not (len(final_merged_vertex_group) > 0) def modal(self, context: Context, event: bpy.types.Event) -> set: if len(self.objects_to_do) > 0: - mesh = self.objects_to_do[0] + bpy.ops.object.select_all(action='DESELECT') + mesh: meshEntry = self.objects_to_do[0] mesh_data: bpy.types.Mesh = mesh["mesh"].data - if len(mesh['shapekeys']) > 0: + if (len(mesh['shapekeys']) > 0) and (not self.advanced): 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+"\".") mesh["mesh"].select_set(True) @@ -95,29 +228,41 @@ class RemoveDoublesSafely(Operator): bpy.ops.object.mode_set(mode='OBJECT') mesh["mesh"].select_set(False) self.objects_to_do.pop(0) + elif (not (mesh["cur_vertex_pass"] > mesh["vertices"])) and self.advanced: + + print("doing a merge by single vertex index at index "+str(mesh["cur_vertex_pass"])) + + if self.modify_mesh_advanced(context, mesh): + mesh["cur_vertex_pass"] = mesh["cur_vertex_pass"]+1 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] + print("finishing double merge object.") + if not self.advanced: 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) + else: + mesh["mesh"].select_set(True) + context.view_layer.objects.active = mesh["mesh"] + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for obj in mesh["mesh_shapekeys"].values(): + obj.select_set(True) + bpy.ops.object.delete() #delete all objects that were shapekey types. + + self.objects_to_do.pop(0) + + + else: self.report({'INFO'}, t("Optimization.remove_doubles_completed")) + print("finishing modal execution of merge doubles safely.") return {'FINISHED'} return {'RUNNING_MODAL'} diff --git a/ui/tools.py b/ui/tools.py index 7d8dc73..62dde6c 100644 --- a/ui/tools.py +++ b/ui/tools.py @@ -33,7 +33,7 @@ class AvatarToolkitToolsPanel(bpy.types.Panel): row.operator(CreateDigitigradeLegs.bl_idname, text=t("Tools.create_digitigrade_legs.label"), icon='BONE_DATA') layout.separator() row = layout.row(align=True) - layout.label(text=t("Tools.separate_by.label"), icon='MESH') + layout.label(text=t("Tools.separate_by.label"), icon='MESH_DATA') row.operator(SeparateByMaterials.bl_idname, text=t("Tools.separate_by_materials.label"), icon='MATERIAL') row.operator(SeparateByLooseParts.bl_idname, text=t("Tools.separate_by_loose_parts.label"), icon='OUTLINER_OB_MESH') row = layout.row(align=True) From 05b90dfefbf96b954adf29b7ff045ab12d27db30 Mon Sep 17 00:00:00 2001 From: Onan Chew Date: Sun, 1 Sep 2024 20:26:54 -0400 Subject: [PATCH 2/2] Advanced mode finished Finally this took forever. Now this is the double merger to end all double mergers. for now. --- functions/import_anything.py | 2 +- functions/remove_doubles_safely.py | 165 +++++++++++++++++------------ resources/translations/en_US.json | 4 +- ui/optimization.py | 5 +- 4 files changed, 105 insertions(+), 71 deletions(-) diff --git a/functions/import_anything.py b/functions/import_anything.py index 96d0351..13216a8 100644 --- a/functions/import_anything.py +++ b/functions/import_anything.py @@ -83,7 +83,7 @@ class ImportAnyModel(Operator, ImportHelper): -#This needs to be done with our own MMD importer: +#TODO: This needs to be done with our own MMD importer. """ #stolen from cats. Oh wait I made this code riiiiiiight - @989onan @register_wrap diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py index d00f71a..887a22b 100644 --- a/functions/remove_doubles_safely.py +++ b/functions/remove_doubles_safely.py @@ -1,9 +1,6 @@ -from ast import Dict -from itertools import count import bpy -import re -from typing import List, Tuple, Optional, TypedDict, Any -from bpy.types import Material, Operator, Context, Object +from typing import List, TypedDict, Any +from bpy.types import Operator, Context, Object from ..core.register import register_wrap from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes from ..functions.translations import t @@ -13,9 +10,26 @@ class meshEntry(TypedDict): shapekeys: list[str] vertices: int cur_vertex_pass: int - mesh_shapekeys: dict[str, Object] @register_wrap +class RemoveDoublesSafelyAdvanced(Operator): + bl_idname = "avatar_toolkit.remove_doubles_safely_advanced" + bl_label = t("Optimization.remove_doubles_safely_advanced.label") + bl_description = t("Optimization.remove_doubles_safely_advanced.desc") + bl_options = {'REGISTER', 'UNDO'} + + + merge_distance: bpy.props.FloatProperty(default=0.0001) + + @classmethod + def poll(cls, context: Context) -> bool: + armature = get_selected_armature(context) + return armature is not None and is_valid_armature(armature) + + def execute(self, context: Context): + bpy.ops.avatar_toolkit.remove_doubles_safely('INVOKE_DEFAULT',advanced=True,merge_distance=self.merge_distance) + return {'FINISHED'} +@register_wrap class RemoveDoublesSafely(Operator): bl_idname = "avatar_toolkit.remove_doubles_safely" bl_label = t("Optimization.remove_doubles_safely.label") @@ -44,13 +58,12 @@ class RemoveDoublesSafely(Operator): for mesh in objects: if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]: print("setting up data for object" + mesh.name) - mesh_shapekeys = {"mesh":mesh,"shapekeys":[],"vertices":0,"cur_vertex_pass":0,"mesh_shapekeys":{}} + mesh_shapekeys = {"mesh":mesh,"shapekeys":[],"vertices":0,"cur_vertex_pass":0} mesh_data: bpy.types.Mesh = mesh.data shape: bpy.types.ShapeKey = None mesh_shapekeys["vertices"] = len(mesh_data.vertices) - 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_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) if mesh_data.shape_keys: for shape in mesh_data.shape_keys.key_blocks: @@ -58,20 +71,6 @@ class RemoveDoublesSafely(Operator): if self.advanced: bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') - context.view_layer.objects.active = mesh - mesh.select_set(True) - mesh.active_shape_key_index = mesh_data.shape_keys.key_blocks.find(shape.name) - bpy.ops.object.duplicate() - newobj = context.view_layer.objects.active - 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, apply_mix=False) - newobj.name = shape.name+"_object_is_"+mesh.name - mesh_shapekeys["mesh_shapekeys"][shape.name] = newobj - context.view_layer.objects.active = mesh bpy.ops.object.select_all(action='DESELECT') print("queued data for "+mesh.name+" is: ") @@ -81,7 +80,15 @@ class RemoveDoublesSafely(Operator): return {'FINISHED'} def invoke(self, context: Context, event: bpy.types.Event) -> set: + print("==================") + print("==================") + print("==================") + print("==================") print("starting modal execution of merge doubles safely.") + print("==================") + print("==================") + print("==================") + print("==================") self.execute(context) context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -96,7 +103,7 @@ class RemoveDoublesSafely(Operator): 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!") + print("shapekey has a moved vertex at index \""+str(index)+"\", excluding from simple double merging!") bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='OBJECT') @@ -106,10 +113,10 @@ class RemoveDoublesSafely(Operator): def modify_mesh_advanced(self, context: Context, mesh_entry: meshEntry): final_merged_vertex_group: list[int] = [] - + initialized_final: bool = False for shapekey_name in mesh_entry["shapekeys"]: - mesh = mesh_entry["mesh_shapekeys"][shapekey_name] + mesh = mesh_entry["mesh"] @@ -122,8 +129,17 @@ class RemoveDoublesSafely(Operator): vertices_original: dict[int,Any] = {} original_count: int = len(mesh_data.vertices) mesh.select_set(True) + mesh.active_shape_key_index = mesh_data.shape_keys.key_blocks.find(shapekey_name) bpy.ops.object.duplicate() + 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, apply_mix=False) + mesh = context.view_layer.objects.active + mesh.name = shapekey_name+"_object_is_"+mesh.name bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') @@ -139,15 +155,8 @@ class RemoveDoublesSafely(Operator): vertices_original[index] = merged_point.co.xyz - #if point.co.xyz != original_mesh_data.shape_keys.key_blocks[0].points[index].co.xyz: - # mesh_data.vertices[index].select = True - # break - print("vertex indices and their positions.") - print(vertices_original) - print("vertex positions end.") - 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_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) select_target_vertex = [False]*len(mesh_data.vertices) try: @@ -155,53 +164,83 @@ class RemoveDoublesSafely(Operator): except: bpy.ops.object.delete() #remove our double merge testing object for this shapekey, since we merged doubles on it, it will be useless. return True - print("vertex select list:") - print(select_target_vertex) - bpy.ops.object.mode_set(mode='EDIT') - mesh_data.vertices.foreach_set("select",select_target_vertex) - bpy.ops.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=True, use_sharp_edge_from_normals=False) bpy.ops.object.mode_set(mode='OBJECT') - #this doesn't keep in mind vertices that are exactly in the same place in the shapekey positional wise that also appear right after one another in the array - #Based on my internal thoughts and theories, this will cause problems. I don't know the solution to this, so it's fine for now. - # besides, the chance of this happening should be very very slim, and will require user input to fix. - @989onan - # { - # "1":"0,0,0" - # "2":"0,0,0" - # "3":"1,0,0" - # } + mesh_data.vertices.foreach_set("select",select_target_vertex) + bpy.ops.object.mode_set(mode='EDIT') + for i in range(0,20): #for some reason, if using merge to unselected on a vertex, the vertex will only merge to 1 other vertex. so we gotta spam it to fix it. + bpy.ops.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=True, use_sharp_edge_from_normals=False) + bpy.ops.object.mode_set(mode='OBJECT') merged_vertices: list[int] = [] + mesh_data_vertices: dict[int,Any] = {} + for idx,vertex in enumerate(mesh_data.vertices): + mesh_data_vertices[idx] = vertex.co.xyz + + #I'm loosing my mind with indices because I cannot keep so many numbers in my head. I will have to use 2 pointers + # yes this can be simplified more, but the mountains of errors with using a normal for statement are making me + # loose my mind. This is hard. - @989onan + #Below is the magic that determines whether or not vertices were merged and then puts the vertices + #that were merged into a list. - @989onan - for i in range(0,original_count): - if mesh_data.vertices[i+len(merged_vertices)].co.xyz != vertices_original[i]: + i = 0 + j = 0 + while(i len(mesh_data.vertices): + merged_vertices.append(i) + j = j-1 + elif mesh_data.vertices[j].co.xyz != vertices_original[i]: + merged_vertices.append(i) + j = j-1 + elif vertices_original[i] == vertices_original[mesh_entry["cur_vertex_pass"]]: merged_vertices.append(i) + i = i+1 + j = j+1 + + + + #give our final set of points some inital data. we're looking for points that are merged on every shape key (and therefore appear in every version of merged_vertices). + # If we initialize the array with points from the first version of merged_vertices, then we can remove the vertices from final that don't get merged from + #every future version of merged_vertices with the "if merged_point not in merged_vertices:" code. + if initialized_final == False: + for point in merged_vertices: + final_merged_vertex_group.append(point) + initialized_final = True #iterate through a copy of final vertex groups to prevent crash. If a vertex was merged before, but didn't merge in this vertex, # then the vertex shouldn't be merged because it moves away from the vertex we are double merging now (ex: bottom of mouth moving away from top when opening on a shapekey) - @989onan for merged_point in final_merged_vertex_group[:]: if merged_point not in merged_vertices: final_merged_vertex_group.remove(merged_point) - else: - final_merged_vertex_group.append(merged_point) + + - bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.object.mode_set(mode='OBJECT') mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.delete() #remove our double merge testing object for this shapekey, since we merged doubles on it, it will be useless. context.view_layer.objects.active = mesh_entry["mesh"] - bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + context.view_layer.objects.active = mesh_entry["mesh"] + mesh_entry["mesh"].select_set(True) + original_mesh_data: bpy.types.Mesh = mesh_entry["mesh"].data - select_target_group = [False]*len(mesh_data.vertices) + select_target_group = [False]*len(original_mesh_data.vertices) + + for vertex_index in final_merged_vertex_group: select_target_group[vertex_index] = True - original_mesh_data.vertices.foreach_set("select",select_target_group) - bpy.ops.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=False, use_sharp_edge_from_normals=False) - mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices)) + bpy.ops.object.mode_set(mode='OBJECT') - print("finished shapekey advanced.") - return not (len(final_merged_vertex_group) > 0) + original_mesh_data.vertices.foreach_set("select",select_target_group) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=False, use_sharp_edge_from_normals=False) + bpy.ops.object.mode_set(mode='OBJECT') + original_mesh_data.vertices.foreach_set("select",[False]*len(original_mesh_data.vertices)) + print("finished advanced merge doubles for single vertex at index: "+str(mesh_entry["cur_vertex_pass"])) + return not (len(final_merged_vertex_group) > 1) def modal(self, context: Context, event: bpy.types.Event) -> set: if len(self.objects_to_do) > 0: @@ -246,14 +285,6 @@ class RemoveDoublesSafely(Operator): bpy.ops.object.mode_set(mode='OBJECT') mesh["mesh"].select_set(False) - else: - mesh["mesh"].select_set(True) - context.view_layer.objects.active = mesh["mesh"] - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - for obj in mesh["mesh_shapekeys"].values(): - obj.select_set(True) - bpy.ops.object.delete() #delete all objects that were shapekey types. self.objects_to_do.pop(0) diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index a0ec7b9..0317753 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -52,8 +52,10 @@ "Optimization.processing_mesh_no_shapekeys": "Processing mesh with no shapekeys named \"{mesh_name}\"", "Optimization.processing_shapekey": "Processing shapekey \"{shapekeyname}\" on mesh \"{mesh_name}\"", "Optimization.remove_doubles_completed": "Remove doubles operation completed", - "Optimization.remove_doubles_safely.desc": "Remove duplicate vertices while preserving important features like mouth shapes", + "Optimization.remove_doubles_safely.desc": "Remove duplicate vertices while preserving important features like mouth shapes.\nIs a quick solution but does not merge vertices that move at all.", "Optimization.remove_doubles_safely.label": "Remove Doubles Safely", + "Optimization.remove_doubles_safely_advanced.label": "Advanced Remove Doubles Safely", + "Optimization.remove_doubles_safely_advanced.desc": "Remove duplicate vertices while preserving important features like mouth shapes.\nUnlike basic, Advanced will merge vertices together that move, but still preserve shapekeys.\nEx: It will not seal the lips of the mouth closed, but will fix split polygons that make up the lips.", "Optimization.select_armature": "Please select an armature", "Optimization.select_at_least_two_meshes": "Please select at least two mesh objects", "Optimization.selected_meshes_joined": "Selected meshes joined successfully", diff --git a/ui/optimization.py b/ui/optimization.py index 3a618bd..6b1d1b3 100644 --- a/ui/optimization.py +++ b/ui/optimization.py @@ -2,6 +2,7 @@ import bpy from ..core.register import register_wrap from .panel import AvatarToolkitPanel from ..functions.translations import t +from ..functions.remove_doubles_safely import RemoveDoublesSafely, RemoveDoublesSafelyAdvanced from ..core.common import get_selected_armature @register_wrap @@ -26,8 +27,8 @@ class AvatarToolkitOptimizationPanel(bpy.types.Panel): row.operator("avatar_toolkit.combine_materials", text=t("Optimization.combine_materials.label"), icon='MATERIAL') row = layout.row(align=True) row.scale_y = 1.2 - row.operator("avatar_toolkit.remove_doubles_safely", text=t("Optimization.remove_doubles_safely.label"), icon='SNAP_VERTEX') - + row.operator(RemoveDoublesSafely.bl_idname, text=t("Optimization.remove_doubles_safely.label"), icon='SNAP_VERTEX') + row.operator(RemoveDoublesSafelyAdvanced.bl_idname, text=t("Optimization.remove_doubles_safely_advanced.label"), icon = "ACTION") layout.separator(factor=0.5) layout.label(text=t("Optimization.joinmeshes.label"), icon='SETTINGS')