Update for Depsgraph API changes
[blender-addons-contrib.git] / amaranth / modeling / symmetry_tools.py
1 #  This program is free software; you can redistribute it and/or
2 #  modify it under the terms of the GNU General Public License
3 #  as published by the Free Software Foundation; either version 2
4 #  of the License, or (at your option) any later version.
5 #
6 #  This program is distributed in the hope that it will be useful,
7 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
8 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 #  GNU General Public License for more details.
10 #
11 #  You should have received a copy of the GNU General Public License
12 #  along with this program; if not, write to the Free Software Foundation,
13 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 """
15 Symmetry Tools: Find Asymmetric + Make Symmetric (by Sergey Sharybin)
16
17 Our character wasn’t completely symmetric in some parts where it was
18 supposed to, this could be by moving vertices by mistake or just reasons.
19 To fix this in a fast way, Sergey coded this two super useful tools:
20
21 * Find Asymmetric:
22 Selects vertices that don’t have the same position on the opposite side.
23
24 * Make Symmetric:
25 Move selected vertices to match the position of those on the other side.
26
27 This tools may not apply on every single model out there, but I tried it
28 in many different characters and it worked. So probably better use it on
29 those models that were already symmetric at some point, modeled with a
30 mirror modifier or so.
31 Search (spacebar) for "Find Asymmetric", and "Make Symmetric""Settings".
32
33 > Developed during Caminandes Open Movie Project
34 """
35
36 import bpy
37 import bmesh
38 from mathutils import Vector
39
40
41 class AMTH_MESH_OT_find_asymmetric(bpy.types.Operator):
42
43     """
44     Find asymmetric vertices
45     """
46
47     bl_idname = "mesh.find_asymmetric"
48     bl_label = "Find Asymmetric"
49     bl_options = {"UNDO", "REGISTER"}
50
51     @classmethod
52     def poll(cls, context):
53         object = context.object
54         if object:
55             return object.mode == "EDIT" and object.type == "MESH"
56         return False
57
58     def execute(self, context):
59         threshold = 1e-6
60
61         object = context.object
62         bm = bmesh.from_edit_mesh(object.data)
63
64         # Deselect all the vertices
65         for v in bm.verts:
66             v.select = False
67
68         for v1 in bm.verts:
69             if abs(v1.co[0]) < threshold:
70                 continue
71
72             mirror_found = False
73             for v2 in bm.verts:
74                 if v1 == v2:
75                     continue
76                 if v1.co[0] * v2.co[0] > 0.0:
77                     continue
78
79                 mirror_coord = Vector(v2.co)
80                 mirror_coord[0] *= -1
81                 if (mirror_coord - v1.co).length_squared < threshold:
82                     mirror_found = True
83                     break
84             if not mirror_found:
85                 v1.select = True
86
87         bm.select_flush_mode()
88
89         bmesh.update_edit_mesh(object.data)
90
91         return {"FINISHED"}
92
93
94 class AMTH_MESH_OT_make_symmetric(bpy.types.Operator):
95
96     """
97     Make symmetric
98     """
99
100     bl_idname = "mesh.make_symmetric"
101     bl_label = "Make Symmetric"
102     bl_options = {"UNDO", "REGISTER"}
103
104     @classmethod
105     def poll(cls, context):
106         object = context.object
107         if object:
108             return object.mode == "EDIT" and object.type == "MESH"
109         return False
110
111     def execute(self, context):
112         threshold = 1e-6
113
114         object = context.object
115         bm = bmesh.from_edit_mesh(object.data)
116
117         for v1 in bm.verts:
118             if v1.co[0] < threshold:
119                 continue
120             if not v1.select:
121                 continue
122
123             closest_vert = None
124             closest_distance = -1
125             for v2 in bm.verts:
126                 if v1 == v2:
127                     continue
128                 if v2.co[0] > threshold:
129                     continue
130                 if not v2.select:
131                     continue
132
133                 mirror_coord = Vector(v2.co)
134                 mirror_coord[0] *= -1
135                 distance = (mirror_coord - v1.co).length_squared
136                 if closest_vert is None or distance < closest_distance:
137                     closest_distance = distance
138                     closest_vert = v2
139
140             if closest_vert:
141                 closest_vert.select = False
142                 closest_vert.co = Vector(v1.co)
143                 closest_vert.co[0] *= -1
144             v1.select = False
145
146         for v1 in bm.verts:
147             if v1.select:
148                 closest_vert = None
149                 closest_distance = -1
150                 for v2 in bm.verts:
151                     if v1 != v2:
152                         mirror_coord = Vector(v2.co)
153                         mirror_coord[0] *= -1
154                         distance = (mirror_coord - v1.co).length_squared
155                         if closest_vert is None or distance < closest_distance:
156                             closest_distance = distance
157                             closest_vert = v2
158                 if closest_vert:
159                     v1.select = False
160                     v1.co = Vector(closest_vert.co)
161                     v1.co[0] *= -1
162
163         bm.select_flush_mode()
164         bmesh.update_edit_mesh(object.data)
165
166         return {"FINISHED"}
167
168
169 def register():
170     bpy.utils.register_class(AMTH_MESH_OT_find_asymmetric)
171     bpy.utils.register_class(AMTH_MESH_OT_make_symmetric)
172
173
174 def unregister():
175     bpy.utils.unregister_class(AMTH_MESH_OT_find_asymmetric)
176     bpy.utils.unregister_class(AMTH_MESH_OT_make_symmetric)