CMake: add WITH_LINKER_LLD option for unix platforms
[blender-staging.git] / release / scripts / startup / bl_operators / mesh.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 import bpy
22 from bpy.types import Operator
23
24 from bpy.props import (
25     EnumProperty,
26     IntProperty,
27 )
28
29
30 class MeshMirrorUV(Operator):
31     """Copy mirror UV coordinates on the X axis based on a mirrored mesh"""
32     bl_idname = "mesh.faces_mirror_uv"
33     bl_label = "Copy Mirrored UV Coords"
34     bl_options = {'REGISTER', 'UNDO'}
35
36     direction: EnumProperty(
37         name="Axis Direction",
38         items=(
39             ('POSITIVE', "Positive", ""),
40             ('NEGATIVE', "Negative", ""),
41         ),
42     )
43
44     precision: IntProperty(
45         name="Precision",
46         description=("Tolerance for finding vertex duplicates"),
47         min=1, max=16,
48         soft_min=1, soft_max=16,
49         default=3,
50     )
51
52     # Returns has_active_UV_layer, double_warn.
53     def do_mesh_mirror_UV(self, mesh, DIR):
54         precision = self.precision
55         double_warn = 0
56
57         if not mesh.uv_layers.active:
58             # has_active_UV_layer, double_warn
59             return False, 0
60
61         # mirror lookups
62         mirror_gt = {}
63         mirror_lt = {}
64
65         vcos = (v.co.to_tuple(precision) for v in mesh.vertices)
66
67         for i, co in enumerate(vcos):
68             if co[0] >= 0.0:
69                 double_warn += co in mirror_gt
70                 mirror_gt[co] = i
71             if co[0] <= 0.0:
72                 double_warn += co in mirror_lt
73                 mirror_lt[co] = i
74
75         vmap = {}
76         for mirror_a, mirror_b in ((mirror_gt, mirror_lt),
77                                    (mirror_lt, mirror_gt)):
78             for co, i in mirror_a.items():
79                 nco = (-co[0], co[1], co[2])
80                 j = mirror_b.get(nco)
81                 if j is not None:
82                     vmap[i] = j
83
84         polys = mesh.polygons
85         loops = mesh.loops
86         uv_loops = mesh.uv_layers.active.data
87         nbr_polys = len(polys)
88
89         mirror_pm = {}
90         pmap = {}
91         puvs = [None] * nbr_polys
92         puvs_cpy = [None] * nbr_polys
93         puvsel = [None] * nbr_polys
94         pcents = [None] * nbr_polys
95         vidxs = [None] * nbr_polys
96         for i, p in enumerate(polys):
97             lstart = lend = p.loop_start
98             lend += p.loop_total
99             puvs[i] = tuple(uv.uv for uv in uv_loops[lstart:lend])
100             puvs_cpy[i] = tuple(uv.copy() for uv in puvs[i])
101             puvsel[i] = (False not in
102                          (uv.select for uv in uv_loops[lstart:lend]))
103             # Vert idx of the poly.
104             vidxs[i] = tuple(l.vertex_index for l in loops[lstart:lend])
105             pcents[i] = p.center
106             # Preparing next step finding matching polys.
107             mirror_pm[tuple(sorted(vidxs[i]))] = i
108
109         for i in range(nbr_polys):
110             # Find matching mirror poly.
111             tvidxs = [vmap.get(j) for j in vidxs[i]]
112             if None not in tvidxs:
113                 tvidxs.sort()
114                 j = mirror_pm.get(tuple(tvidxs))
115                 if j is not None:
116                     pmap[i] = j
117
118         for i, j in pmap.items():
119             if not puvsel[i] or not puvsel[j]:
120                 continue
121             elif DIR == 0 and pcents[i][0] < 0.0:
122                 continue
123             elif DIR == 1 and pcents[i][0] > 0.0:
124                 continue
125
126             # copy UVs
127             uv1 = puvs[i]
128             uv2 = puvs_cpy[j]
129
130             # get the correct rotation
131             v1 = vidxs[j]
132             v2 = tuple(vmap[k] for k in vidxs[i])
133
134             if len(v1) == len(v2):
135                 for k in range(len(v1)):
136                     k_map = v1.index(v2[k])
137                     uv1[k].xy = - (uv2[k_map].x - 0.5) + 0.5, uv2[k_map].y
138
139         # has_active_UV_layer, double_warn
140         return True, double_warn
141
142     @classmethod
143     def poll(cls, context):
144         obj = context.view_layer.objects.active
145         return (obj and obj.type == 'MESH')
146
147     def execute(self, context):
148         DIR = (self.direction == 'NEGATIVE')
149
150         total_no_active_UV = 0
151         total_duplicates = 0
152         meshes_with_duplicates = 0
153
154         ob = context.view_layer.objects.active
155         is_editmode = (ob.mode == 'EDIT')
156         if is_editmode:
157             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
158
159         meshes = [ob.data for ob in context.view_layer.objects.selected
160                   if ob.type == 'MESH' and ob.data.library is None]
161
162         for mesh in meshes:
163             mesh.tag = False
164
165         for mesh in meshes:
166             if mesh.tag:
167                 continue
168
169             mesh.tag = True
170
171             has_active_UV_layer, double_warn = self.do_mesh_mirror_UV(mesh, DIR)
172
173             if not has_active_UV_layer:
174                 total_no_active_UV = total_no_active_UV + 1
175
176             elif double_warn:
177                 total_duplicates += double_warn
178                 meshes_with_duplicates = meshes_with_duplicates + 1
179
180         if is_editmode:
181             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
182
183         if total_duplicates and total_no_active_UV:
184             self.report({'WARNING'}, "%d %s with no active UV layer. "
185                         "%d duplicates found in %d %s, mirror may be incomplete."
186                         % (total_no_active_UV,
187                            "mesh" if total_no_active_UV == 1 else "meshes",
188                            total_duplicates,
189                            meshes_with_duplicates,
190                            "mesh" if meshes_with_duplicates == 1 else "meshes"))
191         elif total_no_active_UV:
192             self.report({'WARNING'}, "%d %s with no active UV layer."
193                         % (total_no_active_UV,
194                            "mesh" if total_no_active_UV == 1 else "meshes"))
195         elif total_duplicates:
196             self.report({'WARNING'}, "%d duplicates found in %d %s,"
197                         " mirror may be incomplete."
198                         % (total_duplicates,
199                            meshes_with_duplicates,
200                            "mesh" if meshes_with_duplicates == 1 else "meshes"))
201
202         return {'FINISHED'}
203
204
205 class MeshSelectNext(Operator):
206     """Select the next element (using selection order)"""
207     bl_idname = "mesh.select_next_item"
208     bl_label = "Select Next Element"
209     bl_options = {'REGISTER', 'UNDO'}
210
211     @classmethod
212     def poll(cls, context):
213         return (context.mode == 'EDIT_MESH')
214
215     def execute(self, context):
216         import bmesh
217         from .bmesh import find_adjacent
218
219         obj = context.active_object
220         me = obj.data
221         bm = bmesh.from_edit_mesh(me)
222
223         if find_adjacent.select_next(bm, self.report):
224             bm.select_flush_mode()
225             bmesh.update_edit_mesh(me, False)
226
227         return {'FINISHED'}
228
229
230 class MeshSelectPrev(Operator):
231     """Select the previous element (using selection order)"""
232     bl_idname = "mesh.select_prev_item"
233     bl_label = "Select Previous Element"
234     bl_options = {'REGISTER', 'UNDO'}
235
236     @classmethod
237     def poll(cls, context):
238         return (context.mode == 'EDIT_MESH')
239
240     def execute(self, context):
241         import bmesh
242         from .bmesh import find_adjacent
243
244         obj = context.active_object
245         me = obj.data
246         bm = bmesh.from_edit_mesh(me)
247
248         if find_adjacent.select_prev(bm, self.report):
249             bm.select_flush_mode()
250             bmesh.update_edit_mesh(me, False)
251
252         return {'FINISHED'}
253
254
255 classes = (
256     MeshMirrorUV,
257     MeshSelectNext,
258     MeshSelectPrev,
259 )