178 lines
5.2 KiB
Python
178 lines
5.2 KiB
Python
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|
"""
|
|||
|
|
Symmetry Tools: Find Asymmetric + Make Symmetric (by Sergey Sharybin)
|
|||
|
|
|
|||
|
|
Our character wasn’t completely symmetric in some parts where it was
|
|||
|
|
supposed to, this could be by moving vertices by mistake or just reasons.
|
|||
|
|
To fix this in a fast way, Sergey coded this two super useful tools:
|
|||
|
|
|
|||
|
|
* Find Asymmetric:
|
|||
|
|
Selects vertices that don’t have the same position on the opposite side.
|
|||
|
|
|
|||
|
|
* Make Symmetric:
|
|||
|
|
Move selected vertices to match the position of those on the other side.
|
|||
|
|
|
|||
|
|
This tools may not apply on every single model out there, but I tried it
|
|||
|
|
in many different characters and it worked. So probably better use it on
|
|||
|
|
those models that were already symmetric at some point, modeled with a
|
|||
|
|
mirror modifier or so.
|
|||
|
|
Search (spacebar) for "Find Asymmetric", and "Make Symmetric""Settings".
|
|||
|
|
|
|||
|
|
> Developed during Caminandes Open Movie Project
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import bpy
|
|||
|
|
import bmesh
|
|||
|
|
from mathutils import Vector
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AMTH_MESH_OT_find_asymmetric(bpy.types.Operator):
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
Find asymmetric vertices
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
bl_idname = "mesh.find_asymmetric"
|
|||
|
|
bl_label = "Find Asymmetric"
|
|||
|
|
bl_options = {"UNDO", "REGISTER"}
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def poll(cls, context):
|
|||
|
|
object = context.object
|
|||
|
|
if object:
|
|||
|
|
return object.mode == "EDIT" and object.type == "MESH"
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def execute(self, context):
|
|||
|
|
threshold = 1e-6
|
|||
|
|
|
|||
|
|
object = context.object
|
|||
|
|
bm = bmesh.from_edit_mesh(object.data)
|
|||
|
|
|
|||
|
|
# Deselect all the vertices
|
|||
|
|
for v in bm.verts:
|
|||
|
|
v.select = False
|
|||
|
|
|
|||
|
|
for v1 in bm.verts:
|
|||
|
|
if abs(v1.co[0]) < threshold:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
mirror_found = False
|
|||
|
|
for v2 in bm.verts:
|
|||
|
|
if v1 == v2:
|
|||
|
|
continue
|
|||
|
|
if v1.co[0] * v2.co[0] > 0.0:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
mirror_coord = Vector(v2.co)
|
|||
|
|
mirror_coord[0] *= -1
|
|||
|
|
if (mirror_coord - v1.co).length_squared < threshold:
|
|||
|
|
mirror_found = True
|
|||
|
|
break
|
|||
|
|
if not mirror_found:
|
|||
|
|
v1.select = True
|
|||
|
|
|
|||
|
|
bm.select_flush_mode()
|
|||
|
|
|
|||
|
|
bmesh.update_edit_mesh(object.data)
|
|||
|
|
|
|||
|
|
return {"FINISHED"}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AMTH_MESH_OT_make_symmetric(bpy.types.Operator):
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
Make symmetric
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
bl_idname = "mesh.make_symmetric"
|
|||
|
|
bl_label = "Make Symmetric"
|
|||
|
|
bl_options = {"UNDO", "REGISTER"}
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def poll(cls, context):
|
|||
|
|
object = context.object
|
|||
|
|
if object:
|
|||
|
|
return object.mode == "EDIT" and object.type == "MESH"
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def execute(self, context):
|
|||
|
|
threshold = 1e-6
|
|||
|
|
|
|||
|
|
object = context.object
|
|||
|
|
bm = bmesh.from_edit_mesh(object.data)
|
|||
|
|
|
|||
|
|
for v1 in bm.verts:
|
|||
|
|
if v1.co[0] < threshold:
|
|||
|
|
continue
|
|||
|
|
if not v1.select:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
closest_vert = None
|
|||
|
|
closest_distance = -1
|
|||
|
|
for v2 in bm.verts:
|
|||
|
|
if v1 == v2:
|
|||
|
|
continue
|
|||
|
|
if v2.co[0] > threshold:
|
|||
|
|
continue
|
|||
|
|
if not v2.select:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
mirror_coord = Vector(v2.co)
|
|||
|
|
mirror_coord[0] *= -1
|
|||
|
|
distance = (mirror_coord - v1.co).length_squared
|
|||
|
|
if closest_vert is None or distance < closest_distance:
|
|||
|
|
closest_distance = distance
|
|||
|
|
closest_vert = v2
|
|||
|
|
|
|||
|
|
if closest_vert:
|
|||
|
|
closest_vert.select = False
|
|||
|
|
closest_vert.co = Vector(v1.co)
|
|||
|
|
closest_vert.co[0] *= -1
|
|||
|
|
v1.select = False
|
|||
|
|
|
|||
|
|
for v1 in bm.verts:
|
|||
|
|
if v1.select:
|
|||
|
|
closest_vert = None
|
|||
|
|
closest_distance = -1
|
|||
|
|
for v2 in bm.verts:
|
|||
|
|
if v1 != v2:
|
|||
|
|
mirror_coord = Vector(v2.co)
|
|||
|
|
mirror_coord[0] *= -1
|
|||
|
|
distance = (mirror_coord - v1.co).length_squared
|
|||
|
|
if closest_vert is None or distance < closest_distance:
|
|||
|
|
closest_distance = distance
|
|||
|
|
closest_vert = v2
|
|||
|
|
if closest_vert:
|
|||
|
|
v1.select = False
|
|||
|
|
v1.co = Vector(closest_vert.co)
|
|||
|
|
v1.co[0] *= -1
|
|||
|
|
|
|||
|
|
bm.select_flush_mode()
|
|||
|
|
bmesh.update_edit_mesh(object.data)
|
|||
|
|
|
|||
|
|
return {"FINISHED"}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ui_symmetry_tools(self, context):
|
|||
|
|
if bpy.context.mode == 'EDIT_MESH':
|
|||
|
|
self.layout.separator()
|
|||
|
|
self.layout.operator(
|
|||
|
|
AMTH_MESH_OT_find_asymmetric.bl_idname,
|
|||
|
|
icon="ALIGN_CENTER", text="Find Asymmetric")
|
|||
|
|
self.layout.operator(
|
|||
|
|
AMTH_MESH_OT_make_symmetric.bl_idname,
|
|||
|
|
icon="ALIGN_JUSTIFY", text="Make Symmetric")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def register():
|
|||
|
|
bpy.utils.register_class(AMTH_MESH_OT_find_asymmetric)
|
|||
|
|
bpy.utils.register_class(AMTH_MESH_OT_make_symmetric)
|
|||
|
|
bpy.types.VIEW3D_MT_edit_mesh.append(ui_symmetry_tools)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def unregister():
|
|||
|
|
bpy.utils.unregister_class(AMTH_MESH_OT_find_asymmetric)
|
|||
|
|
bpy.utils.unregister_class(AMTH_MESH_OT_make_symmetric)
|
|||
|
|
bpy.types.VIEW3D_MT_edit_mesh.remove(ui_symmetry_tools)
|