2025-07-01
This commit is contained in:
@@ -0,0 +1,804 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user