348 lines
11 KiB
Python
348 lines
11 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
__author__ = "Nutti <nutti.metro@gmail.com>"
|
|
__status__ = "production"
|
|
__version__ = "6.6"
|
|
__date__ = "22 Apr 2022"
|
|
|
|
import bpy
|
|
from bpy.props import BoolProperty, FloatProperty, EnumProperty
|
|
import bmesh
|
|
|
|
from .. import common
|
|
from ..utils.bl_class_registry import BlClassRegistry
|
|
from ..utils.property_class_registry import PropertyClassRegistry
|
|
from ..utils import compatibility as compat
|
|
|
|
|
|
def _is_valid_context(context):
|
|
# 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
|
|
# If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
|
|
# after the execution
|
|
if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
|
|
return False
|
|
|
|
objs = common.get_uv_editable_objects(context)
|
|
if not objs:
|
|
return False
|
|
|
|
# only edit mode is allowed to execute
|
|
if context.object.mode != 'EDIT':
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
@PropertyClassRegistry()
|
|
class _Properties:
|
|
idname = "select_uv"
|
|
|
|
@classmethod
|
|
def init_props(cls, scene):
|
|
scene.muv_select_uv_enabled = BoolProperty(
|
|
name="Select UV Enabled",
|
|
description="Select UV is enabled",
|
|
default=False
|
|
)
|
|
scene.muv_select_uv_same_polygon_threshold = FloatProperty(
|
|
name="Same Polygon Threshold",
|
|
description="Threshold to distinguish same polygons",
|
|
default=0.00001,
|
|
min=0.00001,
|
|
max=0.01,
|
|
step=0.00001
|
|
)
|
|
scene.muv_select_uv_selection_method = EnumProperty(
|
|
name="Selection Method",
|
|
description="How to select faces which have overlapped UVs",
|
|
items=[
|
|
('EXTEND', "Extend",
|
|
"Select faces without unselecting selected faces"),
|
|
('RESET', "Reset", "Select faces and unselect selected faces"),
|
|
],
|
|
default='RESET'
|
|
)
|
|
scene.muv_select_uv_sync_mesh_selection = BoolProperty(
|
|
name="Sync Mesh Selection",
|
|
description="Select the mesh's faces as well as UV's faces",
|
|
default=False
|
|
)
|
|
|
|
@classmethod
|
|
def del_props(cls, scene):
|
|
del scene.muv_select_uv_enabled
|
|
del scene.muv_select_uv_same_polygon_threshold
|
|
|
|
|
|
@BlClassRegistry()
|
|
@compat.make_annotations
|
|
class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator):
|
|
"""
|
|
Operation class: Select faces which have overlapped UVs
|
|
"""
|
|
|
|
bl_idname = "uv.muv_select_uv_select_overlapped"
|
|
bl_label = "Overlapped"
|
|
bl_description = "Select faces which have overlapped UVs"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
same_polygon_threshold = FloatProperty(
|
|
name="Same Polygon Threshold",
|
|
description="Threshold to distinguish same polygons",
|
|
default=0.00001,
|
|
min=0.00001,
|
|
max=0.01,
|
|
step=0.00001
|
|
)
|
|
selection_method = EnumProperty(
|
|
name="Selection Method",
|
|
description="How to select faces which have overlapped UVs",
|
|
items=[
|
|
('EXTEND', "Extend",
|
|
"Select faces without unselecting selected faces"),
|
|
('RESET', "Reset", "Select faces and unselect selected faces"),
|
|
],
|
|
default='RESET'
|
|
)
|
|
sync_mesh_selection = BoolProperty(
|
|
name="Sync Mesh Selection",
|
|
description="Select mesh's faces as well as UV's faces",
|
|
default=False
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# we can not get area/space/region from console
|
|
if common.is_console_mode():
|
|
return True
|
|
return _is_valid_context(context)
|
|
|
|
@staticmethod
|
|
def setup_argument(ops, scene):
|
|
ops.same_polygon_threshold = scene.muv_select_uv_same_polygon_threshold
|
|
ops.selection_method = scene.muv_select_uv_selection_method
|
|
ops.sync_mesh_selection = scene.muv_select_uv_sync_mesh_selection
|
|
|
|
def execute(self, context):
|
|
objs = common.get_uv_editable_objects(context)
|
|
|
|
bm_list = []
|
|
uv_layer_list = []
|
|
faces_list = []
|
|
for obj in objs:
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
if common.check_version(2, 73, 0) >= 0:
|
|
bm.faces.ensure_lookup_table()
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
|
|
if context.tool_settings.use_uv_select_sync:
|
|
sel_faces = [f for f in bm.faces]
|
|
else:
|
|
sel_faces = [f for f in bm.faces if f.select]
|
|
bm_list.append(bm)
|
|
uv_layer_list.append(uv_layer)
|
|
faces_list.append(sel_faces)
|
|
|
|
overlapped_info = common.get_overlapped_uv_info(
|
|
bm_list, faces_list, uv_layer_list, 'FACE',
|
|
self.same_polygon_threshold)
|
|
|
|
if self.selection_method == 'RESET':
|
|
if context.tool_settings.use_uv_select_sync:
|
|
for faces in faces_list:
|
|
for f in faces:
|
|
f.select = False
|
|
else:
|
|
for uv_layer, faces in zip(uv_layer_list, faces_list):
|
|
for f in faces:
|
|
if self.sync_mesh_selection:
|
|
f.select = False
|
|
for l in f.loops:
|
|
l[uv_layer].select = False
|
|
|
|
for info in overlapped_info:
|
|
if context.tool_settings.use_uv_select_sync:
|
|
info["subject_face"].select = True
|
|
else:
|
|
if self.sync_mesh_selection:
|
|
info["subject_face"].select = True
|
|
for l in info["subject_face"].loops:
|
|
l[info["subject_uv_layer"]].select = True
|
|
|
|
for obj in objs:
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
@BlClassRegistry()
|
|
@compat.make_annotations
|
|
class MUV_OT_SelectUV_SelectFlipped(bpy.types.Operator):
|
|
"""
|
|
Operation class: Select faces which have flipped UVs
|
|
"""
|
|
|
|
bl_idname = "uv.muv_select_uv_select_flipped"
|
|
bl_label = "Flipped"
|
|
bl_description = "Select faces which have flipped UVs"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
selection_method = EnumProperty(
|
|
name="Selection Method",
|
|
description="How to select faces which have overlapped UVs",
|
|
items=[
|
|
('EXTEND', "Extend",
|
|
"Select faces without unselecting selected faces"),
|
|
('RESET', "Reset", "Select faces and unselect selected faces"),
|
|
],
|
|
default='RESET'
|
|
)
|
|
sync_mesh_selection = BoolProperty(
|
|
name="Sync Mesh Selection",
|
|
description="Select mesh's faces as well as UV's faces",
|
|
default=False
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# we can not get area/space/region from console
|
|
if common.is_console_mode():
|
|
return True
|
|
return _is_valid_context(context)
|
|
|
|
@staticmethod
|
|
def setup_argument(ops, scene):
|
|
ops.selection_method = scene.muv_select_uv_selection_method
|
|
ops.sync_mesh_selection = scene.muv_select_uv_sync_mesh_selection
|
|
|
|
def execute(self, context):
|
|
objs = common.get_uv_editable_objects(context)
|
|
|
|
bm_list = []
|
|
uv_layer_list = []
|
|
faces_list = []
|
|
for obj in objs:
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
if common.check_version(2, 73, 0) >= 0:
|
|
bm.faces.ensure_lookup_table()
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
|
|
if context.tool_settings.use_uv_select_sync:
|
|
sel_faces = [f for f in bm.faces]
|
|
else:
|
|
sel_faces = [f for f in bm.faces if f.select]
|
|
bm_list.append(bm)
|
|
uv_layer_list.append(uv_layer)
|
|
faces_list.append(sel_faces)
|
|
|
|
flipped_info = common.get_flipped_uv_info(
|
|
bm_list, faces_list, uv_layer_list)
|
|
|
|
if self.selection_method == 'RESET':
|
|
if context.tool_settings.use_uv_select_sync:
|
|
for faces in faces_list:
|
|
for f in faces:
|
|
f.select = False
|
|
else:
|
|
for uv_layer, faces in zip(uv_layer_list, faces_list):
|
|
for f in faces:
|
|
if self.sync_mesh_selection:
|
|
f.select = False
|
|
for l in f.loops:
|
|
l[uv_layer].select = False
|
|
|
|
for info in flipped_info:
|
|
if context.tool_settings.use_uv_select_sync:
|
|
info["face"].select = True
|
|
else:
|
|
if self.sync_mesh_selection:
|
|
info["face"].select = True
|
|
for l in info["face"].loops:
|
|
l[info["uv_layer"]].select = True
|
|
|
|
for obj in objs:
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
@BlClassRegistry()
|
|
class MUV_OT_SelectUV_ZoomSelectedUV(bpy.types.Operator):
|
|
"""
|
|
Operation class: Zoom selected UV in View3D space
|
|
"""
|
|
|
|
bl_idname = "uv.muv_select_uv_zoom_selected_uv"
|
|
bl_label = "Zoom Selected UV"
|
|
bl_description = "Zoom selected UV in View3D space"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# we can not get area/space/region from console
|
|
if common.is_console_mode():
|
|
return True
|
|
return _is_valid_context(context)
|
|
|
|
def _get_override_context(self, context):
|
|
for window in context.window_manager.windows:
|
|
screen = window.screen
|
|
for area in screen.areas:
|
|
if area.type == 'VIEW_3D':
|
|
for region in area.regions:
|
|
if region.type == 'WINDOW':
|
|
return {'window': window, 'screen': screen,
|
|
'area': area, 'region': region}
|
|
return None
|
|
|
|
def execute(self, context):
|
|
objs = common.get_uv_editable_objects(context)
|
|
|
|
bm_list = []
|
|
uv_layer_list = []
|
|
verts_list = []
|
|
for obj in objs:
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
if common.check_version(2, 73, 0) >= 0:
|
|
bm.verts.ensure_lookup_table()
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
|
|
sel_verts = [v for v in bm.verts if v.select]
|
|
bm_list.append(bm)
|
|
uv_layer_list.append(uv_layer)
|
|
verts_list.append(sel_verts)
|
|
|
|
# Get all selected UV vertices in UV Editor.
|
|
sel_uv_verts = []
|
|
for vlist, uv_layer in zip(verts_list, uv_layer_list):
|
|
for v in vlist:
|
|
for l in v.link_loops:
|
|
if l[uv_layer].select or \
|
|
context.tool_settings.use_uv_select_sync:
|
|
sel_uv_verts.append(v)
|
|
break
|
|
|
|
# Select vertices only selected in UV Editor.
|
|
for bm in bm_list:
|
|
for v in bm.verts:
|
|
v.select = False
|
|
for v in sel_uv_verts:
|
|
v.select = True
|
|
for obj in objs:
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
# Zoom.
|
|
override_context = self._get_override_context(context)
|
|
if override_context is None:
|
|
self.report({'WARNING'}, "More than one 'VIEW_3D' area must exist")
|
|
return {'CANCELLED'}
|
|
bpy.ops.view3d.view_selected(override_context, use_all_regions=False)
|
|
|
|
# Revert selection of vertices.
|
|
for v in sel_verts:
|
|
v.select = True
|
|
for obj in objs:
|
|
bmesh.update_edit_mesh(obj.data)
|
|
|
|
return {'FINISHED'}
|