eff1d9efe2
almost done, just need to find out why doubles aren't merging even when perfectly together when using merge to unselected
269 lines
13 KiB
Python
269 lines
13 KiB
Python
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 ..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: Object
|
|
shapekeys: list[str]
|
|
vertices: int
|
|
cur_vertex_pass: int
|
|
mesh_shapekeys: dict[str, Object]
|
|
|
|
@register_wrap
|
|
class RemoveDoublesSafely(Operator):
|
|
bl_idname = "avatar_toolkit.remove_doubles_safely"
|
|
bl_label = t("Optimization.remove_doubles_safely.label")
|
|
bl_description = t("Optimization.remove_doubles_safely.desc")
|
|
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:
|
|
armature = get_selected_armature(context)
|
|
return armature is not None and is_valid_armature(armature)
|
|
|
|
def execute(self, context: Context) -> set:
|
|
if not select_current_armature(context):
|
|
self.report({'WARNING'}, t("Optimization.no_armature_selected"))
|
|
return {'CANCELLED'}
|
|
|
|
armature = get_selected_armature(context)
|
|
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]:
|
|
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'}
|
|
|
|
def modify_mesh(self, context: Context, mesh: meshEntry):
|
|
mesh["mesh"].select_set(True)
|
|
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')
|
|
|
|
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:
|
|
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) 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)
|
|
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)
|
|
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:
|
|
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')
|
|
|
|
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'}
|