Advanced mode finished

Finally this took forever.
Now this is the double merger to end all double mergers. for now.
This commit is contained in:
Onan Chew
2024-09-01 20:26:54 -04:00
parent eff1d9efe2
commit 05b90dfefb
4 changed files with 105 additions and 71 deletions
+1 -1
View File
@@ -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 #stolen from cats. Oh wait I made this code riiiiiiight - @989onan
@register_wrap @register_wrap
+96 -65
View File
@@ -1,9 +1,6 @@
from ast import Dict
from itertools import count
import bpy import bpy
import re from typing import List, TypedDict, Any
from typing import List, Tuple, Optional, TypedDict, Any from bpy.types import Operator, Context, Object
from bpy.types import Material, Operator, Context, Object
from ..core.register import register_wrap from ..core.register import register_wrap
from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes
from ..functions.translations import t from ..functions.translations import t
@@ -13,9 +10,26 @@ class meshEntry(TypedDict):
shapekeys: list[str] shapekeys: list[str]
vertices: int vertices: int
cur_vertex_pass: int cur_vertex_pass: int
mesh_shapekeys: dict[str, Object]
@register_wrap @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): class RemoveDoublesSafely(Operator):
bl_idname = "avatar_toolkit.remove_doubles_safely" bl_idname = "avatar_toolkit.remove_doubles_safely"
bl_label = t("Optimization.remove_doubles_safely.label") bl_label = t("Optimization.remove_doubles_safely.label")
@@ -44,13 +58,12 @@ class RemoveDoublesSafely(Operator):
for mesh in objects: for mesh in objects:
if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]: 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) 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 mesh_data: bpy.types.Mesh = mesh.data
shape: bpy.types.ShapeKey = None shape: bpy.types.ShapeKey = None
mesh_shapekeys["vertices"] = len(mesh_data.vertices) 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') bpy.ops.object.mode_set(mode='OBJECT')
mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices))
if mesh_data.shape_keys: if mesh_data.shape_keys:
for shape in mesh_data.shape_keys.key_blocks: for shape in mesh_data.shape_keys.key_blocks:
@@ -58,20 +71,6 @@ class RemoveDoublesSafely(Operator):
if self.advanced: if self.advanced:
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') 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 context.view_layer.objects.active = mesh
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
print("queued data for "+mesh.name+" is: ") print("queued data for "+mesh.name+" is: ")
@@ -81,7 +80,15 @@ class RemoveDoublesSafely(Operator):
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context: Context, event: bpy.types.Event) -> set: def invoke(self, context: Context, event: bpy.types.Event) -> set:
print("==================")
print("==================")
print("==================")
print("==================")
print("starting modal execution of merge doubles safely.") print("starting modal execution of merge doubles safely.")
print("==================")
print("==================")
print("==================")
print("==================")
self.execute(context) self.execute(context)
context.window_manager.modal_handler_add(self) context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@@ -96,7 +103,7 @@ class RemoveDoublesSafely(Operator):
for index, point in enumerate(mesh["mesh"].active_shape_key.points): 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: if point.co.xyz != mesh_data.shape_keys.key_blocks[0].points[index].co.xyz:
mesh_data.vertices[index].select = True 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='EDIT')
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
@@ -106,10 +113,10 @@ class RemoveDoublesSafely(Operator):
def modify_mesh_advanced(self, context: Context, mesh_entry: meshEntry): def modify_mesh_advanced(self, context: Context, mesh_entry: meshEntry):
final_merged_vertex_group: list[int] = [] final_merged_vertex_group: list[int] = []
initialized_final: bool = False
for shapekey_name in mesh_entry["shapekeys"]: 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] = {} vertices_original: dict[int,Any] = {}
original_count: int = len(mesh_data.vertices) original_count: int = len(mesh_data.vertices)
mesh.select_set(True) 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.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 = context.view_layer.objects.active
mesh.name = shapekey_name+"_object_is_"+mesh.name
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
@@ -139,15 +155,8 @@ class RemoveDoublesSafely(Operator):
vertices_original[index] = merged_point.co.xyz 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') 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) select_target_vertex = [False]*len(mesh_data.vertices)
try: try:
@@ -155,53 +164,83 @@ class RemoveDoublesSafely(Operator):
except: except:
bpy.ops.object.delete() #remove our double merge testing object for this shapekey, since we merged doubles on it, it will be useless. 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 return True
print("vertex select list:")
print(select_target_vertex)
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='OBJECT')
mesh_data.vertices.foreach_set("select",select_target_vertex) 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.mesh.remove_doubles(threshold=self.merge_distance, use_unselected=True, use_sharp_edge_from_normals=False)
bpy.ops.object.mode_set(mode='OBJECT') 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] = [] 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
for i in range(0,original_count): #I'm loosing my mind with indices because I cannot keep so many numbers in my head. I will have to use 2 pointers
if mesh_data.vertices[i+len(merged_vertices)].co.xyz != vertices_original[i]: # 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
i = 0
j = 0
while(i<len(vertices_original)):
if j+1 > 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) 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, #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 # 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[:]: for merged_point in final_merged_vertex_group[:]:
if merged_point not in merged_vertices: if merged_point not in merged_vertices:
final_merged_vertex_group.remove(merged_point) 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)) mesh_data.vertices.foreach_set("select",[False]*len(mesh_data.vertices))
bpy.ops.object.mode_set(mode='OBJECT') 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. 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"] 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 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: for vertex_index in final_merged_vertex_group:
select_target_group[vertex_index] = True 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') bpy.ops.object.mode_set(mode='OBJECT')
print("finished shapekey advanced.") original_mesh_data.vertices.foreach_set("select",select_target_group)
return not (len(final_merged_vertex_group) > 0) 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: def modal(self, context: Context, event: bpy.types.Event) -> set:
if len(self.objects_to_do) > 0: if len(self.objects_to_do) > 0:
@@ -246,14 +285,6 @@ class RemoveDoublesSafely(Operator):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
mesh["mesh"].select_set(False) 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) self.objects_to_do.pop(0)
+3 -1
View File
@@ -52,8 +52,10 @@
"Optimization.processing_mesh_no_shapekeys": "Processing mesh with no shapekeys named \"{mesh_name}\"", "Optimization.processing_mesh_no_shapekeys": "Processing mesh with no shapekeys named \"{mesh_name}\"",
"Optimization.processing_shapekey": "Processing shapekey \"{shapekeyname}\" on mesh \"{mesh_name}\"", "Optimization.processing_shapekey": "Processing shapekey \"{shapekeyname}\" on mesh \"{mesh_name}\"",
"Optimization.remove_doubles_completed": "Remove doubles operation completed", "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.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_armature": "Please select an armature",
"Optimization.select_at_least_two_meshes": "Please select at least two mesh objects", "Optimization.select_at_least_two_meshes": "Please select at least two mesh objects",
"Optimization.selected_meshes_joined": "Selected meshes joined successfully", "Optimization.selected_meshes_joined": "Selected meshes joined successfully",
+3 -2
View File
@@ -2,6 +2,7 @@ import bpy
from ..core.register import register_wrap from ..core.register import register_wrap
from .panel import AvatarToolkitPanel from .panel import AvatarToolkitPanel
from ..functions.translations import t from ..functions.translations import t
from ..functions.remove_doubles_safely import RemoveDoublesSafely, RemoveDoublesSafelyAdvanced
from ..core.common import get_selected_armature from ..core.common import get_selected_armature
@register_wrap @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.operator("avatar_toolkit.combine_materials", text=t("Optimization.combine_materials.label"), icon='MATERIAL')
row = layout.row(align=True) row = layout.row(align=True)
row.scale_y = 1.2 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.separator(factor=0.5)
layout.label(text=t("Optimization.joinmeshes.label"), icon='SETTINGS') layout.label(text=t("Optimization.joinmeshes.label"), icon='SETTINGS')