forked from franMarz/TexTools-Blender
-
Notifications
You must be signed in to change notification settings - Fork 0
/
op_select_islands_identical.py
148 lines (114 loc) · 4.14 KB
/
op_select_islands_identical.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import bpy
import bmesh
from . import utilities_uv
class op(bpy.types.Operator):
bl_idname = "uv.textools_select_islands_identical"
bl_label = "Select similar"
bl_description = "Select UV islands with similar topology with respect to the selected UVs"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if bpy.context.area.ui_type != 'UV':
return False
if not bpy.context.active_object:
return False
if bpy.context.active_object.type != 'MESH':
return False
if bpy.context.active_object.mode != 'EDIT':
return False
if not bpy.context.object.data.uv_layers:
return False
return True
def execute(self, context):
island_stats_source_list = utilities_uv.multi_object_loop(island_find, self, context, need_results = True)
if not island_stats_source_list:
return {'CANCELLED'}
elif {'CANCELLED'} in island_stats_source_list:
return {'CANCELLED'}
elif len(list(filter(bool, island_stats_source_list))) > 1:
self.report({'ERROR_INVALID_INPUT'}, "Please select only 1 UV Island")
return {'CANCELLED'}
island = None
for island_stats_source in island_stats_source_list:
if island_stats_source:
island = island_stats_source
break
if island:
utilities_uv.multi_object_loop(swap, self, context, island)
return {'FINISHED'}
def island_find(self, context):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
islands = utilities_uv.get_selected_islands(bm, uv_layers, selected=False, extend_selection_to_islands=True)
if not islands:
return {}
if len(islands) > 1:
self.report({'ERROR_INVALID_INPUT'}, "Please select only 1 UV Island")
return {'CANCELLED'}
island_stats_source = Island_stats(bm, islands[0])
return island_stats_source
def swap(self, context, island_stats_source):
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
sync = bpy.context.scene.tool_settings.use_uv_select_sync
if sync:
selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode)
else:
selection_mode = bpy.context.scene.tool_settings.uv_select_mode
if sync:
bpy.ops.mesh.select_all(action='SELECT')
else:
bpy.ops.uv.select_all(action='SELECT')
islands_all = utilities_uv.get_selected_islands(bm, uv_layers)
islands_equal = []
for island in islands_all:
island_stats = Island_stats(bm, island)
if island_stats_source.isEqual(island_stats):
islands_equal.append(island)
if sync:
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
for island in islands_equal:
for face in island:
face.select_set(True)
else:
bpy.ops.uv.select_all(action='DESELECT')
for island in islands_equal:
for face in island:
for loop in face.loops:
loop[uv_layers].select = True
if sync:
bpy.context.scene.tool_settings.mesh_select_mode = selection_mode
else:
# Workaround for selection not flushing properly from loops to EDGE Selection Mode, apparently since UV edge selection support was added to the UV space
bpy.ops.uv.select_mode(type='VERTEX')
bpy.context.scene.tool_settings.uv_select_mode = selection_mode
class Island_stats:
countFaces = 0
countVerts = 0
area = 0
countLinkedEdges = 0
countLinkedFaces = 0
def __init__(self, bm, faces):
# Collect topology stats
self.countFaces = len(faces)
verts = {v for f in faces for v in f.verts}
self.countVerts = len(verts)
for vert in verts:
self.countLinkedEdges += len(vert.link_edges)
self.countLinkedFaces += len(vert.link_faces)
for face in faces:
self.area += face.calc_area()
def isEqual(self, other):
if self.countVerts != other.countVerts:
return False
if self.countFaces != other.countFaces:
return False
if self.countLinkedEdges != other.countLinkedEdges:
return False
if self.countLinkedFaces != other.countLinkedFaces:
return False
# area needs to be 90%+ identical
if min(self.area, other.area)/max(self.area, other.area) < 0.7:
return False
return True