Node Wrangler: do not add reroutes to unavailable outputs
[blender-addons.git] / magic_uv / op / select_uv.py
blobd351593ced9141a61c5cc4ce933cb6943401c0fc
1 # SPDX-FileCopyrightText: 2018-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 __author__ = "Nutti <nutti.metro@gmail.com>"
6 __status__ = "production"
7 __version__ = "6.6"
8 __date__ = "22 Apr 2022"
10 import bpy
11 from bpy.props import BoolProperty, FloatProperty, EnumProperty
12 import bmesh
14 from .. import common
15 from ..utils.bl_class_registry import BlClassRegistry
16 from ..utils.property_class_registry import PropertyClassRegistry
17 from ..utils import compatibility as compat
20 def _is_valid_context(context):
21 # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
22 # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
23 # after the execution
24 if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
25 return False
27 objs = common.get_uv_editable_objects(context)
28 if not objs:
29 return False
31 # only edit mode is allowed to execute
32 if context.object.mode != 'EDIT':
33 return False
35 return True
38 @PropertyClassRegistry()
39 class _Properties:
40 idname = "select_uv"
42 @classmethod
43 def init_props(cls, scene):
44 scene.muv_select_uv_enabled = BoolProperty(
45 name="Select UV Enabled",
46 description="Select UV is enabled",
47 default=False
49 scene.muv_select_uv_same_polygon_threshold = FloatProperty(
50 name="Same Polygon Threshold",
51 description="Threshold to distinguish same polygons",
52 default=0.00001,
53 min=0.00001,
54 max=0.01,
55 step=0.00001
57 scene.muv_select_uv_selection_method = EnumProperty(
58 name="Selection Method",
59 description="How to select faces which have overlapped UVs",
60 items=[
61 ('EXTEND', "Extend",
62 "Select faces without unselecting selected faces"),
63 ('RESET', "Reset", "Select faces and unselect selected faces"),
65 default='RESET'
67 scene.muv_select_uv_sync_mesh_selection = BoolProperty(
68 name="Sync Mesh Selection",
69 description="Select the mesh's faces as well as UVs' faces",
70 default=False
73 @classmethod
74 def del_props(cls, scene):
75 del scene.muv_select_uv_enabled
76 del scene.muv_select_uv_same_polygon_threshold
79 @BlClassRegistry()
80 @compat.make_annotations
81 class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator):
82 """
83 Operation class: Select faces which have overlapped UVs
84 """
86 bl_idname = "uv.muv_select_uv_select_overlapped"
87 bl_label = "Overlapped"
88 bl_description = "Select faces which have overlapped UVs"
89 bl_options = {'REGISTER', 'UNDO'}
91 same_polygon_threshold = FloatProperty(
92 name="Same Polygon Threshold",
93 description="Threshold to distinguish same polygons",
94 default=0.00001,
95 min=0.00001,
96 max=0.01,
97 step=0.00001
99 selection_method = EnumProperty(
100 name="Selection Method",
101 description="How to select faces which have overlapped UVs",
102 items=[
103 ('EXTEND', "Extend",
104 "Select faces without unselecting selected faces"),
105 ('RESET', "Reset", "Select faces and unselect selected faces"),
107 default='RESET'
109 sync_mesh_selection = BoolProperty(
110 name="Sync Mesh Selection",
111 description="Select the mesh's faces as well as UVs' faces",
112 default=False
115 @classmethod
116 def poll(cls, context):
117 # we can not get area/space/region from console
118 if common.is_console_mode():
119 return True
120 return _is_valid_context(context)
122 @staticmethod
123 def setup_argument(ops, scene):
124 ops.same_polygon_threshold = scene.muv_select_uv_same_polygon_threshold
125 ops.selection_method = scene.muv_select_uv_selection_method
126 ops.sync_mesh_selection = scene.muv_select_uv_sync_mesh_selection
128 def execute(self, context):
129 objs = common.get_uv_editable_objects(context)
131 bm_list = []
132 uv_layer_list = []
133 faces_list = []
134 for obj in objs:
135 bm = bmesh.from_edit_mesh(obj.data)
136 if common.check_version(2, 73, 0) >= 0:
137 bm.faces.ensure_lookup_table()
138 uv_layer = bm.loops.layers.uv.verify()
140 if context.tool_settings.use_uv_select_sync:
141 sel_faces = [f for f in bm.faces]
142 else:
143 sel_faces = [f for f in bm.faces if f.select]
144 bm_list.append(bm)
145 uv_layer_list.append(uv_layer)
146 faces_list.append(sel_faces)
148 overlapped_info = common.get_overlapped_uv_info(
149 bm_list, faces_list, uv_layer_list, 'FACE',
150 self.same_polygon_threshold)
152 if self.selection_method == 'RESET':
153 if context.tool_settings.use_uv_select_sync:
154 for faces in faces_list:
155 for f in faces:
156 f.select = False
157 else:
158 for uv_layer, faces in zip(uv_layer_list, faces_list):
159 for f in faces:
160 if self.sync_mesh_selection:
161 f.select = False
162 for l in f.loops:
163 l[uv_layer].select = False
165 for info in overlapped_info:
166 if context.tool_settings.use_uv_select_sync:
167 info["subject_face"].select = True
168 else:
169 if self.sync_mesh_selection:
170 info["subject_face"].select = True
171 for l in info["subject_face"].loops:
172 l[info["subject_uv_layer"]].select = True
174 for obj in objs:
175 bmesh.update_edit_mesh(obj.data)
177 return {'FINISHED'}
180 @BlClassRegistry()
181 @compat.make_annotations
182 class MUV_OT_SelectUV_SelectFlipped(bpy.types.Operator):
184 Operation class: Select faces which have flipped UVs
187 bl_idname = "uv.muv_select_uv_select_flipped"
188 bl_label = "Flipped"
189 bl_description = "Select faces which have flipped UVs"
190 bl_options = {'REGISTER', 'UNDO'}
192 selection_method = EnumProperty(
193 name="Selection Method",
194 description="How to select faces which have overlapped UVs",
195 items=[
196 ('EXTEND', "Extend",
197 "Select faces without unselecting selected faces"),
198 ('RESET', "Reset", "Select faces and unselect selected faces"),
200 default='RESET'
202 sync_mesh_selection = BoolProperty(
203 name="Sync Mesh Selection",
204 description="Select the mesh's faces as well as UVs' faces",
205 default=False
208 @classmethod
209 def poll(cls, context):
210 # we can not get area/space/region from console
211 if common.is_console_mode():
212 return True
213 return _is_valid_context(context)
215 @staticmethod
216 def setup_argument(ops, scene):
217 ops.selection_method = scene.muv_select_uv_selection_method
218 ops.sync_mesh_selection = scene.muv_select_uv_sync_mesh_selection
220 def execute(self, context):
221 objs = common.get_uv_editable_objects(context)
223 bm_list = []
224 uv_layer_list = []
225 faces_list = []
226 for obj in objs:
227 bm = bmesh.from_edit_mesh(obj.data)
228 if common.check_version(2, 73, 0) >= 0:
229 bm.faces.ensure_lookup_table()
230 uv_layer = bm.loops.layers.uv.verify()
232 if context.tool_settings.use_uv_select_sync:
233 sel_faces = [f for f in bm.faces]
234 else:
235 sel_faces = [f for f in bm.faces if f.select]
236 bm_list.append(bm)
237 uv_layer_list.append(uv_layer)
238 faces_list.append(sel_faces)
240 flipped_info = common.get_flipped_uv_info(
241 bm_list, faces_list, uv_layer_list)
243 if self.selection_method == 'RESET':
244 if context.tool_settings.use_uv_select_sync:
245 for faces in faces_list:
246 for f in faces:
247 f.select = False
248 else:
249 for uv_layer, faces in zip(uv_layer_list, faces_list):
250 for f in faces:
251 if self.sync_mesh_selection:
252 f.select = False
253 for l in f.loops:
254 l[uv_layer].select = False
256 for info in flipped_info:
257 if context.tool_settings.use_uv_select_sync:
258 info["face"].select = True
259 else:
260 if self.sync_mesh_selection:
261 info["face"].select = True
262 for l in info["face"].loops:
263 l[info["uv_layer"]].select = True
265 for obj in objs:
266 bmesh.update_edit_mesh(obj.data)
268 return {'FINISHED'}
271 @BlClassRegistry()
272 class MUV_OT_SelectUV_ZoomSelectedUV(bpy.types.Operator):
274 Operation class: Zoom selected UV in View3D space
277 bl_idname = "uv.muv_select_uv_zoom_selected_uv"
278 bl_label = "Zoom Selected UV"
279 bl_description = "Zoom selected UV in View3D space"
280 bl_options = {'REGISTER', 'UNDO'}
282 @classmethod
283 def poll(cls, context):
284 # we can not get area/space/region from console
285 if common.is_console_mode():
286 return True
287 return _is_valid_context(context)
289 def _get_override_context(self, context):
290 for window in context.window_manager.windows:
291 screen = window.screen
292 for area in screen.areas:
293 if area.type == 'VIEW_3D':
294 for region in area.regions:
295 if region.type == 'WINDOW':
296 return {'window': window, 'screen': screen,
297 'area': area, 'region': region}
298 return None
300 def execute(self, context):
301 objs = common.get_uv_editable_objects(context)
303 bm_list = []
304 uv_layer_list = []
305 verts_list = []
306 for obj in objs:
307 bm = bmesh.from_edit_mesh(obj.data)
308 if common.check_version(2, 73, 0) >= 0:
309 bm.verts.ensure_lookup_table()
310 uv_layer = bm.loops.layers.uv.verify()
312 sel_verts = [v for v in bm.verts if v.select]
313 bm_list.append(bm)
314 uv_layer_list.append(uv_layer)
315 verts_list.append(sel_verts)
317 # Get all selected UV vertices in UV Editor.
318 sel_uv_verts = []
319 for vlist, uv_layer in zip(verts_list, uv_layer_list):
320 for v in vlist:
321 for l in v.link_loops:
322 if l[uv_layer].select or \
323 context.tool_settings.use_uv_select_sync:
324 sel_uv_verts.append(v)
325 break
327 # Select vertices only selected in UV Editor.
328 for bm in bm_list:
329 for v in bm.verts:
330 v.select = False
331 for v in sel_uv_verts:
332 v.select = True
333 for obj in objs:
334 bmesh.update_edit_mesh(obj.data)
336 # Zoom.
337 context_override = self._get_override_context(context)
338 if context_override is None:
339 self.report({'WARNING'}, "More than one 'VIEW_3D' area must exist")
340 return {'CANCELLED'}
341 with context.temp_override(**context_override):
342 bpy.ops.view3d.view_selected(use_all_regions=False)
344 # Revert selection of vertices.
345 for v in sel_verts:
346 v.select = True
347 for obj in objs:
348 bmesh.update_edit_mesh(obj.data)
350 return {'FINISHED'}