805 lines
22 KiB
Python
805 lines
22 KiB
Python
import bpy
|
|
import bmesh
|
|
import math
|
|
import mathutils
|
|
|
|
from mathutils import Vector
|
|
from . import settings
|
|
from . import utilities_ui
|
|
|
|
|
|
precision = 5
|
|
multi_object_loop_stop = False
|
|
|
|
|
|
|
|
def multi_object_loop(func, *args, need_results = False, **kwargs) :
|
|
selected_obs = [ob for ob in bpy.context.selected_objects if ob.type == 'MESH']
|
|
|
|
if len(selected_obs) == 1:
|
|
preactiv_name = bpy.context.view_layer.objects.active.name
|
|
bpy.context.view_layer.objects.active = selected_obs[0]
|
|
if not need_results:
|
|
func(*args, **kwargs)
|
|
if bpy.data.objects[preactiv_name]:
|
|
bpy.context.view_layer.objects.active = bpy.data.objects[preactiv_name]
|
|
else:
|
|
result = func(*args, **kwargs)
|
|
results = [result]
|
|
if bpy.data.objects[preactiv_name]:
|
|
bpy.context.view_layer.objects.active = bpy.data.objects[preactiv_name]
|
|
return results
|
|
|
|
else:
|
|
global multi_object_loop_stop
|
|
multi_object_loop_stop = False
|
|
premode = bpy.context.active_object.mode
|
|
preactiv_name = bpy.context.view_layer.objects.active.name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
|
unique_selected_obs = [ob for ob in bpy.context.objects_in_mode_unique_data if ob.type == 'MESH' and ob.select_get()]
|
|
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
if need_results :
|
|
results = []
|
|
|
|
for ob in unique_selected_obs:
|
|
if multi_object_loop_stop:
|
|
break
|
|
bpy.context.view_layer.objects.active = ob
|
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
|
if "ob_num" in kwargs :
|
|
print("Operating on object " + str(kwargs["ob_num"]))
|
|
if need_results :
|
|
result = func(*args, **kwargs)
|
|
#if result:
|
|
results.append(result)
|
|
else:
|
|
func(*args, **kwargs)
|
|
if "ob_num" in kwargs:
|
|
kwargs["ob_num"] += 1
|
|
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
for ob in selected_obs:
|
|
ob.select_set(True)
|
|
|
|
bpy.context.view_layer.objects.active = bpy.data.objects[preactiv_name]
|
|
bpy.ops.object.mode_set(mode=premode)
|
|
|
|
if need_results :
|
|
return results
|
|
|
|
|
|
|
|
def selection_store(bm=None, uv_layers=None, return_selected_UV_faces=False, return_selected_faces_edges=False, return_selected_faces_loops=False):
|
|
if bm is None:
|
|
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
|
uv_layers = bm.loops.layers.uv.verify()
|
|
|
|
settings.use_uv_sync = bpy.context.scene.tool_settings.use_uv_select_sync
|
|
settings.selection_uv_mode = bpy.context.scene.tool_settings.uv_select_mode
|
|
|
|
contextViewUV = utilities_ui.GetContextViewUV()
|
|
if contextViewUV:
|
|
settings.selection_uv_pivot = contextViewUV['area'].spaces[0].pivot_point
|
|
settings.selection_uv_pivot_pos = contextViewUV['area'].spaces[0].cursor_location.copy()
|
|
|
|
# Clear previous selection
|
|
settings.selection_vert_indexies.clear()
|
|
settings.selection_edge_indexies.clear()
|
|
settings.selection_face_indexies.clear()
|
|
settings.seam_edges.clear()
|
|
|
|
settings.selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode)
|
|
|
|
if settings.selection_mode[0]:
|
|
for vert in bm.verts:
|
|
if vert.select:
|
|
settings.selection_vert_indexies.add(vert.index)
|
|
if settings.selection_mode[1]:
|
|
for edge in bm.edges:
|
|
if edge.select:
|
|
settings.selection_edge_indexies.add(edge.index)
|
|
|
|
# Face selections (Loops)
|
|
settings.selection_uv_loops.clear()
|
|
if return_selected_UV_faces:
|
|
selected_faces = set()
|
|
elif return_selected_faces_edges or return_selected_faces_loops:
|
|
selected_faces_loops = {}
|
|
|
|
for face in bm.faces:
|
|
if face.select:
|
|
settings.selection_face_indexies.add(face.index)
|
|
n_selected_loops = 0
|
|
if return_selected_faces_edges or return_selected_faces_loops:
|
|
face_selected_loops = []
|
|
|
|
for loop in face.loops:
|
|
if loop.edge.seam == True:
|
|
settings.seam_edges.add(loop.edge)
|
|
if loop[uv_layers].select:
|
|
n_selected_loops += 1
|
|
settings.selection_uv_loops.add( (face.index, loop.vert.index) )
|
|
if return_selected_faces_edges or return_selected_faces_loops:
|
|
face_selected_loops.append(loop)
|
|
|
|
if return_selected_UV_faces and n_selected_loops == len(face.loops) and face.select:
|
|
selected_faces.add(face)
|
|
elif return_selected_faces_edges and n_selected_loops == 2 and face.select:
|
|
selected_faces_loops.update({face: face_selected_loops})
|
|
elif return_selected_faces_loops and n_selected_loops > 0 and face.select:
|
|
selected_faces_loops.update({face: face_selected_loops})
|
|
|
|
if return_selected_UV_faces:
|
|
return selected_faces
|
|
elif return_selected_faces_edges or return_selected_faces_loops:
|
|
return selected_faces_loops
|
|
|
|
|
|
|
|
def selection_restore(bm = None, uv_layers = None, restore_seams=False):
|
|
mode = bpy.context.object.mode
|
|
if mode != 'EDIT':
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
if bm is None:
|
|
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
|
uv_layers = bm.loops.layers.uv.verify()
|
|
|
|
bpy.context.scene.tool_settings.use_uv_select_sync = settings.use_uv_sync
|
|
bpy.context.scene.tool_settings.uv_select_mode = settings.selection_uv_mode
|
|
|
|
contextViewUV = utilities_ui.GetContextViewUV()
|
|
if contextViewUV:
|
|
contextViewUV['area'].spaces[0].pivot_point = settings.selection_uv_pivot
|
|
if settings.bversion >= 3.2:
|
|
with bpy.context.temp_override(**contextViewUV):
|
|
bpy.ops.uv.cursor_set(location=settings.selection_uv_pivot_pos)
|
|
else:
|
|
bpy.ops.uv.cursor_set(contextViewUV, location=settings.selection_uv_pivot_pos)
|
|
|
|
#Restore seams
|
|
if restore_seams:
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.mesh.mark_seam(clear=True)
|
|
for edge in settings.seam_edges:
|
|
edge.seam = True
|
|
|
|
bpy.ops.mesh.select_all(action='DESELECT')
|
|
|
|
#Selection Mode
|
|
bpy.context.scene.tool_settings.mesh_select_mode = settings.selection_mode
|
|
|
|
if settings.selection_mode[0]:
|
|
bm.verts.ensure_lookup_table()
|
|
for index in settings.selection_vert_indexies:
|
|
if index < len(bm.verts):
|
|
bm.verts[index].select = True
|
|
if settings.selection_mode[1]:
|
|
bm.edges.ensure_lookup_table()
|
|
for index in settings.selection_edge_indexies:
|
|
if index < len(bm.edges):
|
|
bm.edges[index].select = True
|
|
bm.faces.ensure_lookup_table()
|
|
for index in settings.selection_face_indexies:
|
|
if index < len(bm.faces):
|
|
bm.faces[index].select = True
|
|
|
|
#UV Face-UV Selections (Loops)
|
|
if contextViewUV:
|
|
if settings.bversion >= 3.2:
|
|
with bpy.context.temp_override(**contextViewUV):
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
else:
|
|
bpy.ops.uv.select_all(contextViewUV, action='DESELECT')
|
|
else:
|
|
for face in bm.faces:
|
|
for loop in face.loops:
|
|
loop[uv_layers].select = False
|
|
for uv_set in settings.selection_uv_loops:
|
|
for loop in bm.faces[uv_set[0]].loops:
|
|
if loop.vert.index == uv_set[1]:
|
|
loop[uv_layers].select = True
|
|
break
|
|
|
|
# Workaround for selection not flushing properly from loops in EDGE or FACE UV Selection Mode, apparently since UV edge selection support was added to the UV space
|
|
if settings.selection_uv_mode != "VERTEX":
|
|
bpy.ops.uv.select_mode(type='VERTEX')
|
|
bpy.context.scene.tool_settings.uv_select_mode = settings.selection_uv_mode
|
|
|
|
bpy.context.view_layer.update()
|
|
bpy.ops.object.mode_set(mode=mode)
|
|
|
|
|
|
def selected_unique_objects_in_mode_with_uv():
|
|
return [obj for obj in bpy.context.objects_in_mode_unique_data if obj.type == 'MESH' and obj.data.uv_layers]
|
|
|
|
def get_UDIM_tile_coords(obj):
|
|
udim_tile = 1001
|
|
column = row = 0
|
|
|
|
if bpy.context.scene.texToolsSettings.UDIMs_source == 'OBJECT':
|
|
if obj and obj.type == 'MESH' and obj.data.uv_layers:
|
|
for i in range(len(obj.material_slots)):
|
|
slot = obj.material_slots[i]
|
|
if slot.material:
|
|
if slot.material.use_nodes:
|
|
nodes = slot.material.node_tree.nodes
|
|
if nodes:
|
|
for node in nodes:
|
|
if node.type == 'TEX_IMAGE' and node.image and node.image.source =='TILED':
|
|
udim_tile = node.image.tiles.active.number
|
|
break
|
|
else:
|
|
continue
|
|
break
|
|
else:
|
|
image = bpy.context.space_data.image
|
|
if image:
|
|
udim_tile = image.tiles.active.number
|
|
|
|
if udim_tile != 1001:
|
|
column = int(str(udim_tile - 1)[-1])
|
|
if udim_tile > 1010:
|
|
row = int(str(udim_tile - 1001)[0:-1])
|
|
|
|
return udim_tile, column, row
|
|
|
|
|
|
|
|
def get_UDIM_tiles(objs):
|
|
tiles = set()
|
|
for obj in objs:
|
|
for i in range(len(obj.material_slots)):
|
|
slot = obj.material_slots[i]
|
|
if slot.material:
|
|
if slot.material.use_nodes:
|
|
nodes = slot.material.node_tree.nodes
|
|
if nodes:
|
|
for node in nodes:
|
|
if node.type == 'TEX_IMAGE' and node.image and node.image.source =='TILED':
|
|
tiles.update({tile.number for tile in node.image.tiles})
|
|
return tiles
|
|
|
|
|
|
|
|
def move_island(island, dx, dy):
|
|
me = bpy.context.active_object.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
|
|
# adjust uv coordinates
|
|
for face in island:
|
|
for loop in face.loops:
|
|
loop_uv = loop[uv_layer]
|
|
loop_uv.uv[0] += dx
|
|
loop_uv.uv[1] += dy
|
|
|
|
bmesh.update_edit_mesh(me)
|
|
|
|
|
|
def translate_island(island, uv_layer, delta):
|
|
for face in island:
|
|
for loop in face.loops:
|
|
loop[uv_layer].uv += delta
|
|
|
|
|
|
def rotate_island(island, uv_layer=None, angle=0, pivot=None):
|
|
'''Rotate a list of faces by angle (in radians) around a center'''
|
|
rot_matrix = mathutils.Matrix.Rotation(-angle, 2)
|
|
if uv_layer is None:
|
|
me = bpy.context.active_object.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
if pivot:
|
|
for face in island:
|
|
for loop in face.loops:
|
|
uv = loop[uv_layer]
|
|
uv.uv = rot_matrix @ (uv.uv - pivot) + pivot
|
|
return
|
|
|
|
for face in island:
|
|
for loop in face.loops:
|
|
uv = loop[uv_layer]
|
|
uv.uv = uv.uv @ rot_matrix
|
|
|
|
|
|
def scale_island(island, uv_layer, scale_x, scale_y, pivot=None):
|
|
"""Scale a list of faces by 'scale_x, scale_y'. """
|
|
|
|
if not pivot:
|
|
bbox = get_BBOX(island, None, uv_layer)
|
|
pivot = bbox['center']
|
|
|
|
for face in island:
|
|
for loop in face.loops:
|
|
x, y = loop[uv_layer].uv
|
|
xt = x - pivot.x
|
|
yt = y - pivot.y
|
|
xs = xt * scale_x
|
|
ys = yt * scale_y
|
|
loop[uv_layer].uv.x = xs + pivot.x
|
|
loop[uv_layer].uv.y = ys + pivot.y
|
|
|
|
|
|
def set_selected_faces(faces, bm, uv_layers):
|
|
for face in faces:
|
|
for loop in face.loops:
|
|
loop[uv_layers].select = True
|
|
|
|
|
|
|
|
def get_selected_uvs(bm, uv_layers):
|
|
"""Returns selected mesh vertices of selected UV's"""
|
|
uvs = set()
|
|
for face in bm.faces:
|
|
if face.select:
|
|
for loop in face.loops:
|
|
if loop[uv_layers].select:
|
|
uvs.add( loop[uv_layers] )
|
|
return uvs
|
|
|
|
|
|
|
|
def get_selected_uv_verts(bm, uv_layers, selected=None):
|
|
"""Returns selected mesh vertices of selected UV's"""
|
|
verts = set()
|
|
if selected is None:
|
|
for face in bm.faces:
|
|
if face.select:
|
|
for loop in face.loops:
|
|
if loop[uv_layers].select:
|
|
verts.add( loop.vert )
|
|
else:
|
|
for loop in selected:
|
|
verts.add( loop.vert )
|
|
return verts
|
|
|
|
|
|
|
|
def get_selected_uv_edges(bm, uv_layers, selected=None):
|
|
"""Returns selected mesh edges of selected UV's"""
|
|
verts = get_selected_uv_verts(bm, uv_layers, selected)
|
|
edges = set()
|
|
for edge in bm.edges:
|
|
if edge.verts[0] in verts and edge.verts[1] in verts:
|
|
edges.add(edge)
|
|
return edges
|
|
|
|
|
|
|
|
def get_selected_uv_faces(bm, uv_layers):
|
|
"""Returns selected mesh faces of selected UV's"""
|
|
faces = [face for face in bm.faces if all([loop[uv_layers].select for loop in face.loops]) and face.select]
|
|
return faces
|
|
|
|
|
|
|
|
def get_vert_to_uv(bm, uv_layers):
|
|
vert_to_uv = {}
|
|
for face in bm.faces:
|
|
for loop in face.loops:
|
|
vert = loop.vert
|
|
uv = loop[uv_layers]
|
|
if vert not in vert_to_uv:
|
|
vert_to_uv[vert] = [uv]
|
|
else:
|
|
vert_to_uv[vert].append(uv)
|
|
return vert_to_uv
|
|
|
|
|
|
|
|
def get_uv_to_vert(bm, uv_layers):
|
|
uv_to_vert = {}
|
|
for face in bm.faces:
|
|
for loop in face.loops:
|
|
vert = loop.vert
|
|
uv = loop[uv_layers]
|
|
if uv not in uv_to_vert:
|
|
uv_to_vert[ uv ] = vert
|
|
return uv_to_vert
|
|
|
|
|
|
|
|
def getSelectionBBox(bm=None, uv_layers=None):
|
|
if bm is None:
|
|
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
|
|
|
|
bbox = {}
|
|
|
|
xmin = math.inf
|
|
xmax = -math.inf
|
|
ymin = math.inf
|
|
ymax = -math.inf
|
|
|
|
select = False
|
|
for face in bm.faces:
|
|
if face.select:
|
|
for loop in face.loops:
|
|
if sync or loop[uv_layers].select:
|
|
select = True
|
|
x, y = loop[uv_layers].uv
|
|
if xmin > x:
|
|
xmin = x
|
|
if xmax < x:
|
|
xmax = x
|
|
if ymin > y:
|
|
ymin = y
|
|
if ymax < y:
|
|
ymax = y
|
|
|
|
if not select:
|
|
return bbox
|
|
|
|
bbox['min'] = Vector((xmin, ymin))
|
|
bbox['max'] = Vector((xmax, ymax))
|
|
|
|
bbox['width'] = xmax - xmin
|
|
bbox['height'] = ymax - ymin
|
|
|
|
xcenter = (xmax + xmin)*0.5
|
|
ycenter = (ymax + ymin)*0.5
|
|
|
|
bbox['center'] = Vector((xcenter, ycenter))
|
|
bbox['area'] = bbox['width'] * bbox['height']
|
|
bbox['minLength'] = min(bbox['width'], bbox['height'])
|
|
|
|
return bbox
|
|
|
|
|
|
|
|
def get_BBOX(group, bm, uv_layers, are_loops=False):
|
|
bbox = {}
|
|
xmin = math.inf
|
|
xmax = -math.inf
|
|
ymin = math.inf
|
|
ymax = -math.inf
|
|
|
|
if not are_loops:
|
|
for face in group:
|
|
for loop in face.loops:
|
|
x, y = loop[uv_layers].uv
|
|
if xmin > x:
|
|
xmin = x
|
|
if xmax < x:
|
|
xmax = x
|
|
if ymin > y:
|
|
ymin = y
|
|
if ymax < y:
|
|
ymax = y
|
|
else:
|
|
for loop in group:
|
|
x, y = loop[uv_layers].uv
|
|
if xmin > x:
|
|
xmin = x
|
|
if xmax < x:
|
|
xmax = x
|
|
if ymin > y:
|
|
ymin = y
|
|
if ymax < y:
|
|
ymax = y
|
|
|
|
bbox['min'] = Vector((xmin, ymin))
|
|
bbox['max'] = Vector((xmax, ymax))
|
|
|
|
bbox['width'] = xmax - xmin
|
|
bbox['height'] = ymax - ymin
|
|
|
|
xcenter = (xmax + xmin) * 0.5
|
|
ycenter = (ymax + ymin) * 0.5
|
|
|
|
bbox['center'] = Vector((xcenter, ycenter))
|
|
bbox['area'] = bbox['width'] * bbox['height']
|
|
bbox['minLength'] = min(bbox['width'], bbox['height'])
|
|
|
|
return bbox
|
|
|
|
|
|
|
|
def get_BBOX_multi(all_ob_bounds):
|
|
multibbox = {}
|
|
boundsMin = Vector((math.inf, math.inf))
|
|
boundsMax = Vector((-math.inf, -math.inf))
|
|
boundsCenter = Vector((0.0,0.0))
|
|
|
|
for ob_bounds in all_ob_bounds:
|
|
if len(ob_bounds) > 1 :
|
|
boundsMin.x = min(boundsMin.x, ob_bounds['min'].x)
|
|
boundsMin.y = min(boundsMin.y, ob_bounds['min'].y)
|
|
boundsMax.x = max(boundsMax.x, ob_bounds['max'].x)
|
|
boundsMax.y = max(boundsMax.y, ob_bounds['max'].y)
|
|
|
|
multibbox['min'] = boundsMin
|
|
multibbox['max'] = boundsMax
|
|
multibbox['width'] = (boundsMax - boundsMin).x
|
|
multibbox['height'] = (boundsMax - boundsMin).y
|
|
|
|
boundsCenter.x = (boundsMax.x + boundsMin.x)/2
|
|
boundsCenter.y = (boundsMax.y + boundsMin.y)/2
|
|
|
|
multibbox['center'] = boundsCenter
|
|
multibbox['area'] = multibbox['width'] * multibbox['height']
|
|
multibbox['minLength'] = min(multibbox['width'], multibbox['height'])
|
|
|
|
return multibbox
|
|
|
|
|
|
|
|
def get_center(group, bm, uv_layers, are_loops=False):
|
|
n = 0
|
|
total = Vector((0.0, 0.0))
|
|
|
|
if not are_loops:
|
|
for face in group:
|
|
for loop in face.loops:
|
|
total += loop[uv_layers].uv
|
|
n += 1
|
|
else:
|
|
for loop in group:
|
|
total += loop[uv_layers].uv
|
|
n += 1
|
|
|
|
return total / n
|
|
|
|
|
|
|
|
def get_selected_islands(bm, uv_layers, selected=True, extend_selection_to_islands=False):
|
|
islands = []
|
|
island = set()
|
|
|
|
sync = bpy.context.scene.tool_settings.use_uv_select_sync
|
|
|
|
faces = bm.faces
|
|
# Reset tags for unselected (if tag is False - skip)
|
|
if selected:
|
|
if sync:
|
|
for face in faces:
|
|
face.tag = face.select
|
|
else:
|
|
for face in faces:
|
|
if face.select:
|
|
face.tag = all(l[uv_layers].select for l in face.loops)
|
|
continue
|
|
face.tag = False
|
|
else:
|
|
if sync:
|
|
for face in faces:
|
|
face.tag = not face.hide
|
|
else:
|
|
for face in faces:
|
|
face.tag = not face.hide and face.select
|
|
|
|
for face in faces:
|
|
# Skip unselected and appended faces
|
|
if not face.tag: # if is False:
|
|
continue
|
|
|
|
# Tag first element in island (dont add again)
|
|
face.tag = False
|
|
|
|
# Container collector of island elements
|
|
parts_of_island = [face]
|
|
|
|
# Container for get elements from loop from parts_of_island
|
|
temp = []
|
|
|
|
# Blank list == all faces of the island taken
|
|
while parts_of_island:
|
|
for f in parts_of_island:
|
|
# Running through all the neighboring faces
|
|
for l in f.loops:
|
|
link_face = l.link_loop_radial_next.face
|
|
# Skip appended
|
|
if not link_face.tag: # if is False:
|
|
continue
|
|
|
|
for ll in link_face.loops:
|
|
if not ll.face.tag: # if is False:
|
|
continue
|
|
# If the coordinates of the vertices of adjacent
|
|
# faces on the uv match, then this is part of the
|
|
# island and we append face to the list
|
|
if ll[uv_layers].uv != l[uv_layers].uv:
|
|
continue
|
|
# Skip non-manifold
|
|
if (l.link_loop_next[uv_layers].uv == ll.link_loop_prev[uv_layers].uv) or \
|
|
(ll.link_loop_next[uv_layers].uv == l.link_loop_prev[uv_layers].uv):
|
|
temp.append(ll.face)
|
|
ll.face.tag = False
|
|
|
|
island.update(parts_of_island)
|
|
parts_of_island = temp
|
|
temp = []
|
|
|
|
# Skip the islands that don't have a single selected face.
|
|
if selected is False and extend_selection_to_islands is True:
|
|
if sync:
|
|
for face in island:
|
|
if face.select:
|
|
break
|
|
else:
|
|
island = set()
|
|
continue
|
|
else:
|
|
for face in island:
|
|
if all(l[uv_layers].select for l in face.loops):
|
|
break
|
|
else:
|
|
island = set()
|
|
continue
|
|
|
|
islands.append(island)
|
|
island = set()
|
|
return islands
|
|
|
|
|
|
def getFacesIslands(bm, uv_layers, faces, islands, disordered_island_faces):
|
|
for face in faces:
|
|
if face in disordered_island_faces:
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
face.loops[0][uv_layers].select = True
|
|
bpy.ops.uv.select_linked()
|
|
|
|
islandFaces = {f for f in disordered_island_faces if f.loops[0][uv_layers].select}
|
|
disordered_island_faces.difference_update(islandFaces)
|
|
|
|
islands.append(islandFaces)
|
|
if not disordered_island_faces:
|
|
break
|
|
|
|
|
|
|
|
def getAllIslands(bm, uv_layers):
|
|
faces = {f for f in bm.faces if f.select}
|
|
if not faces:
|
|
return []
|
|
|
|
islands = []
|
|
faces_unparsed = faces.copy()
|
|
|
|
getFacesIslands(bm, uv_layers, faces, islands, faces_unparsed)
|
|
|
|
return islands
|
|
|
|
|
|
|
|
def getSelectionIslands(bm, uv_layers, extend_selection_to_islands=False, selected_faces=None, need_faces_selected=True, restore_selected=True):
|
|
if selected_faces is None:
|
|
if need_faces_selected:
|
|
selected_faces = {f for f in bm.faces if all([l[uv_layers].select for l in f.loops]) and f.select}
|
|
else:
|
|
selected_faces = {f for f in bm.faces if any([l[uv_layers].select for l in f.loops]) and f.select}
|
|
if not selected_faces:
|
|
return []
|
|
|
|
# Select islands
|
|
if extend_selection_to_islands:
|
|
bpy.ops.uv.select_linked()
|
|
disordered_island_faces = {f for f in bm.faces if f.loops[0][uv_layers].select and f.select}
|
|
else:
|
|
disordered_island_faces = selected_faces.copy()
|
|
|
|
# Collect UV islands
|
|
islands = []
|
|
|
|
getFacesIslands(bm, uv_layers, selected_faces, islands, disordered_island_faces)
|
|
|
|
# Restore selection
|
|
if restore_selected:
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
set_selected_faces(selected_faces, bm, uv_layers)
|
|
|
|
return islands
|
|
|
|
|
|
|
|
def getSelectedUnselectedIslands(bm, uv_layers, selected_faces=None, target_faces=None, restore_selected=False):
|
|
if selected_faces is None:
|
|
return [], []
|
|
|
|
# Collect selected UV islands
|
|
selected_islands = []
|
|
bpy.ops.uv.select_linked()
|
|
disordered_islands_selected = {f for f in bm.faces if f.loops[0][uv_layers].select and f.select}
|
|
|
|
getFacesIslands(bm, uv_layers, selected_faces, selected_islands, disordered_islands_selected)
|
|
|
|
# Collect target UV islands
|
|
if target_faces is None:
|
|
return selected_islands, []
|
|
|
|
target_islands = []
|
|
target_faces.difference_update(disordered_islands_selected)
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
for f in target_faces:
|
|
f.loops[0][uv_layers].select = True
|
|
bpy.ops.uv.select_linked()
|
|
disordered_islands_targets = {f for f in bm.faces if f.loops[0][uv_layers].select and f.select}
|
|
|
|
getFacesIslands(bm, uv_layers, target_faces, target_islands, disordered_islands_targets)
|
|
|
|
if restore_selected:
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
set_selected_faces(selected_faces, bm, uv_layers)
|
|
|
|
return selected_islands, target_islands
|
|
|
|
|
|
|
|
def getSelectionFacesIslands(bm, uv_layers, selected_faces_loops):
|
|
# Select islands
|
|
bpy.ops.uv.select_linked()
|
|
disordered_island_faces = {f for f in bm.faces if f.loops[0][uv_layers].select and f.select}
|
|
|
|
# Collect UV islands
|
|
selected_faces_islands = {}
|
|
to_remove = set()
|
|
|
|
for face in selected_faces_loops.keys():
|
|
if face not in disordered_island_faces:
|
|
to_remove.add(face)
|
|
else:
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
face.loops[0][uv_layers].select = True
|
|
bpy.ops.uv.select_linked()
|
|
|
|
face_island = {f for f in disordered_island_faces if f.loops[0][uv_layers].select}
|
|
disordered_island_faces.difference_update(face_island)
|
|
|
|
selected_faces_islands.update({face: face_island})
|
|
|
|
for face in to_remove:
|
|
selected_faces_loops.pop(face)
|
|
|
|
return selected_faces_islands, selected_faces_loops
|
|
|
|
|
|
'''
|
|
def getSelectionLoopsIslands(bm, uv_layers, selected_loops):
|
|
# Select islands
|
|
bpy.ops.uv.select_linked()
|
|
disordered_loops_islands = {loop for face in bm.faces for loop in face.loops if loop[uv_layers].select and loop.edge.select}
|
|
|
|
selected_loops_islands = []
|
|
|
|
for loop in selected_loops:
|
|
if loop in disordered_loops_islands:
|
|
bpy.ops.uv.select_all(action='DESELECT')
|
|
loop[uv_layers].select = True
|
|
bpy.ops.uv.select_linked()
|
|
|
|
loops_island = {l for l in disordered_loops_islands if l[uv_layers].select}
|
|
disordered_loops_islands.difference_update(loops_island)
|
|
|
|
selected_loops_islands.append(loops_island)
|
|
if not disordered_loops_islands:
|
|
break
|
|
|
|
return selected_loops_islands
|
|
'''
|
|
|
|
def find_min_rotate_angle(angle):
|
|
angle = math.degrees(angle)
|
|
x = math.fmod(angle, 90)
|
|
if angle > 45:
|
|
y = 90 - x
|
|
angle = -y if y < x else x
|
|
elif angle < -45:
|
|
y = -90 - x
|
|
angle = -y if y > x else x
|
|
|
|
return math.radians(angle)
|
|
|
|
def calc_min_align_angle(selected_faces, uv_layers):
|
|
points = [l[uv_layers].uv for f in selected_faces for l in f.loops]
|
|
align_angle_pre = mathutils.geometry.box_fit_2d(points)
|
|
return find_min_rotate_angle(align_angle_pre)
|
|
|
|
def calc_min_align_angle_pt(points):
|
|
align_angle_pre = mathutils.geometry.box_fit_2d(points)
|
|
return find_min_rotate_angle(align_angle_pre)
|