Replace plain-text type information with the type syntax used for Python's type annotations as it's more concise, especially for callbacks which often didn't include useful type information. Note that this change only applies to inline doc-strings, generated doc-strings from RNA need to be updated separately. Details: - Many minor corrections were made when "list" was incorrectly used instead of "sequence". - Some type information wasn't defined in the doc-strings and has been added. - Verbose type info would benefit from support for type aliases.
465 lines
14 KiB
Python
465 lines
14 KiB
Python
# SPDX-FileCopyrightText: 2011-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
__all__ = (
|
|
"mesh_linked_uv_islands",
|
|
"mesh_linked_triangles",
|
|
"edge_face_count_dict",
|
|
"edge_face_count",
|
|
"edge_loops_from_edges",
|
|
"ngon_tessellate",
|
|
"triangle_random_points",
|
|
)
|
|
|
|
|
|
def mesh_linked_uv_islands(mesh):
|
|
"""
|
|
Returns lists of polygon indices connected by UV islands.
|
|
|
|
:arg mesh: the mesh used to group with.
|
|
:type mesh: :class:`bpy.types.Mesh`
|
|
:return: list of lists containing polygon indices
|
|
:rtype: list[list[int]]
|
|
"""
|
|
|
|
if mesh.polygons and not mesh.uv_layers.active.data:
|
|
# Currently, when in edit mode, UV Layer data will always be empty
|
|
# when accessed though RNA. This may change in the future.
|
|
raise ValueError(
|
|
"UV Layers are not currently available from python in Edit Mode. "
|
|
"Use bmesh and bpy_extras.bmesh_utils.bmesh_linked_uv_islands instead."
|
|
)
|
|
|
|
uv_loops = [luv.uv[:] for luv in mesh.uv_layers.active.data]
|
|
poly_loops = [poly.loop_indices for poly in mesh.polygons]
|
|
luv_hash = {}
|
|
luv_hash_get = luv_hash.get
|
|
luv_hash_ls = [None] * len(uv_loops)
|
|
for pi, poly_indices in enumerate(poly_loops):
|
|
for li in poly_indices:
|
|
uv = uv_loops[li]
|
|
uv_hub = luv_hash_get(uv)
|
|
if uv_hub is None:
|
|
uv_hub = luv_hash[uv] = [pi]
|
|
else:
|
|
uv_hub.append(pi)
|
|
luv_hash_ls[li] = uv_hub
|
|
|
|
poly_islands = []
|
|
|
|
# 0 = none, 1 = added, 2 = searched
|
|
poly_tag = [0] * len(poly_loops)
|
|
|
|
while True:
|
|
poly_index = -1
|
|
for i in range(len(poly_loops)):
|
|
if poly_tag[i] == 0:
|
|
poly_index = i
|
|
break
|
|
|
|
if poly_index != -1:
|
|
island = [poly_index]
|
|
poly_tag[poly_index] = 1
|
|
poly_islands.append(island)
|
|
else:
|
|
break # we're done
|
|
|
|
added = True
|
|
while added:
|
|
added = False
|
|
for poly_index in island[:]:
|
|
if poly_tag[poly_index] == 1:
|
|
for li in poly_loops[poly_index]:
|
|
for poly_index_shared in luv_hash_ls[li]:
|
|
if poly_tag[poly_index_shared] == 0:
|
|
added = True
|
|
poly_tag[poly_index_shared] = 1
|
|
island.append(poly_index_shared)
|
|
poly_tag[poly_index] = 2
|
|
|
|
return poly_islands
|
|
|
|
|
|
def mesh_linked_triangles(mesh):
|
|
"""
|
|
Splits the mesh into connected triangles, use this for separating cubes from
|
|
other mesh elements within 1 mesh data-block.
|
|
|
|
:arg mesh: the mesh used to group with.
|
|
:type mesh: :class:`bpy.types.Mesh`
|
|
:return: Lists of lists containing triangles.
|
|
:rtype: list[list[:class:`bpy.types.MeshLoopTriangle`]]
|
|
"""
|
|
|
|
# Build vert face connectivity
|
|
vert_tris = [[] for i in range(len(mesh.vertices))]
|
|
for t in mesh.loop_triangles:
|
|
for v in t.vertices:
|
|
vert_tris[v].append(t)
|
|
|
|
# sort triangles into connectivity groups
|
|
tri_groups = [[t] for t in mesh.loop_triangles]
|
|
# map old, new tri location
|
|
tri_mapping = list(range(len(mesh.loop_triangles)))
|
|
|
|
# Now clump triangles iteratively
|
|
ok = True
|
|
while ok:
|
|
ok = False
|
|
|
|
for t in mesh.loop_triangles:
|
|
mapped_index = tri_mapping[t.index]
|
|
mapped_group = tri_groups[mapped_index]
|
|
|
|
for v in t.vertices:
|
|
for nxt_t in vert_tris[v]:
|
|
if nxt_t != t:
|
|
nxt_mapped_index = tri_mapping[nxt_t.index]
|
|
|
|
# We are not a part of the same group
|
|
if mapped_index != nxt_mapped_index:
|
|
ok = True
|
|
|
|
# Assign mapping to this group so they
|
|
# all map to this group
|
|
for grp_t in tri_groups[nxt_mapped_index]:
|
|
tri_mapping[grp_t.index] = mapped_index
|
|
|
|
# Move triangles into this group
|
|
mapped_group.extend(tri_groups[nxt_mapped_index])
|
|
|
|
# remove reference to the list
|
|
tri_groups[nxt_mapped_index] = None
|
|
|
|
# return all tri groups that are not null
|
|
# this is all the triangles that are connected in their own lists.
|
|
return [tg for tg in tri_groups if tg]
|
|
|
|
|
|
def edge_face_count_dict(mesh):
|
|
"""
|
|
:return: Dictionary of edge keys with their value set to the number of faces using each edge.
|
|
:rtype: dict[tuple[int, int], int]
|
|
"""
|
|
|
|
face_edge_count = {}
|
|
loops = mesh.loops
|
|
edges = mesh.edges
|
|
for poly in mesh.polygons:
|
|
for i in poly.loop_indices:
|
|
key = edges[loops[i].edge_index].key
|
|
try:
|
|
face_edge_count[key] += 1
|
|
except:
|
|
face_edge_count[key] = 1
|
|
|
|
return face_edge_count
|
|
|
|
|
|
def edge_face_count(mesh):
|
|
"""
|
|
:return: list face users for each item in mesh.edges.
|
|
:rtype: list[int]
|
|
"""
|
|
edge_face_count = edge_face_count_dict(mesh)
|
|
get = dict.get
|
|
return [get(edge_face_count, ed.key, 0) for ed in mesh.edges]
|
|
|
|
|
|
def edge_loops_from_edges(mesh, edges=None):
|
|
"""
|
|
Edge loops defined by edges
|
|
|
|
Takes me.edges or a list of edges and returns the edge loops
|
|
|
|
return a list of vertex indices.
|
|
[ [1, 6, 7, 2], ...]
|
|
|
|
closed loops have matching start and end values.
|
|
"""
|
|
line_polys = []
|
|
|
|
# Get edges not used by a face
|
|
if edges is None:
|
|
edges = mesh.edges
|
|
|
|
if not hasattr(edges, "pop"):
|
|
edges = edges[:]
|
|
|
|
while edges:
|
|
current_edge = edges.pop()
|
|
vert_end, vert_start = current_edge.vertices[:]
|
|
line_poly = [vert_start, vert_end]
|
|
|
|
ok = True
|
|
while ok:
|
|
ok = False
|
|
# for i, ed in enumerate(edges):
|
|
i = len(edges)
|
|
while i:
|
|
i -= 1
|
|
ed = edges[i]
|
|
v1, v2 = ed.vertices
|
|
if v1 == vert_end:
|
|
line_poly.append(v2)
|
|
vert_end = line_poly[-1]
|
|
ok = 1
|
|
del edges[i]
|
|
# break
|
|
elif v2 == vert_end:
|
|
line_poly.append(v1)
|
|
vert_end = line_poly[-1]
|
|
ok = 1
|
|
del edges[i]
|
|
# break
|
|
elif v1 == vert_start:
|
|
line_poly.insert(0, v2)
|
|
vert_start = line_poly[0]
|
|
ok = 1
|
|
del edges[i]
|
|
# break
|
|
elif v2 == vert_start:
|
|
line_poly.insert(0, v1)
|
|
vert_start = line_poly[0]
|
|
ok = 1
|
|
del edges[i]
|
|
# break
|
|
line_polys.append(line_poly)
|
|
|
|
return line_polys
|
|
|
|
|
|
def ngon_tessellate(from_data, indices, fix_loops=True, debug_print=True):
|
|
"""
|
|
Takes a poly-line of indices (ngon) and returns a list of face
|
|
index lists. Designed to be used for importers that need indices for an
|
|
ngon to create from existing verts.
|
|
|
|
:arg from_data: Either a mesh, or a list/tuple of 3D vectors.
|
|
:type from_data: :class:`bpy.types.Mesh` | list[Sequence[float]] | tuple[Sequence[float]]
|
|
:arg indices: a list of indices to use this list
|
|
is the ordered closed poly-line
|
|
to fill, and can be a subset of the data given.
|
|
:type indices: list[int]
|
|
:arg fix_loops: If this is enabled poly-lines
|
|
that use loops to make multiple
|
|
poly-lines are dealt with correctly.
|
|
:type fix_loops: bool
|
|
"""
|
|
|
|
from mathutils.geometry import tessellate_polygon
|
|
from mathutils import Vector
|
|
vector_to_tuple = Vector.to_tuple
|
|
|
|
if not indices:
|
|
return []
|
|
|
|
def mlen(co):
|
|
# Manhatten length of a vector, faster then length.
|
|
return abs(co[0]) + abs(co[1]) + abs(co[2])
|
|
|
|
def vert_from_vector_with_extra_data(v, i):
|
|
# Calculate data per-vector, for reuse.
|
|
return v, vector_to_tuple(v, 6), i, mlen(v)
|
|
|
|
def ed_key_mlen(v1, v2):
|
|
if v1[3] > v2[3]:
|
|
return v2[1], v1[1]
|
|
else:
|
|
return v1[1], v2[1]
|
|
|
|
if not fix_loops:
|
|
# Normal single concave loop filling.
|
|
|
|
if type(from_data) in {tuple, list}:
|
|
verts = [Vector(from_data[i]) for ii, i in enumerate(indices)]
|
|
else:
|
|
verts = [from_data.vertices[i].co for ii, i in enumerate(indices)]
|
|
|
|
# same as reversed(range(1, len(verts))):
|
|
for i in range(len(verts) - 1, 0, -1):
|
|
if verts[i][1] == verts[i - 1][0]:
|
|
verts.pop(i - 1)
|
|
|
|
fill = tessellate_polygon([verts])
|
|
|
|
else:
|
|
# Separate this loop into multiple loops be finding edges that are
|
|
# used twice. This is used by Light-Wave LWO files a lot.
|
|
|
|
if type(from_data) in {tuple, list}:
|
|
verts = [
|
|
vert_from_vector_with_extra_data(Vector(from_data[i]), ii)
|
|
for ii, i in enumerate(indices)
|
|
]
|
|
else:
|
|
verts = [
|
|
vert_from_vector_with_extra_data(from_data.vertices[i].co, ii)
|
|
for ii, i in enumerate(indices)
|
|
]
|
|
|
|
edges = [(i, i - 1) for i in range(len(verts))]
|
|
if edges:
|
|
edges[0] = (0, len(verts) - 1)
|
|
|
|
if not verts:
|
|
return []
|
|
|
|
edges_used = set()
|
|
edges_doubles = set()
|
|
# We need to check if any edges are used twice location based.
|
|
for ed in edges:
|
|
edkey = ed_key_mlen(verts[ed[0]], verts[ed[1]])
|
|
if edkey in edges_used:
|
|
edges_doubles.add(edkey)
|
|
else:
|
|
edges_used.add(edkey)
|
|
|
|
# Store a list of unconnected loop segments split by double edges.
|
|
# will join later
|
|
loop_segments = []
|
|
|
|
v_prev = verts[0]
|
|
context_loop = [v_prev]
|
|
loop_segments = [context_loop]
|
|
|
|
for v in verts:
|
|
if v != v_prev:
|
|
# Are we crossing an edge we removed?
|
|
if ed_key_mlen(v, v_prev) in edges_doubles:
|
|
context_loop = [v]
|
|
loop_segments.append(context_loop)
|
|
else:
|
|
if context_loop and context_loop[-1][1] == v[1]:
|
|
pass
|
|
else:
|
|
context_loop.append(v)
|
|
|
|
v_prev = v
|
|
# Now join loop segments
|
|
|
|
def join_seg(s1, s2):
|
|
if s2[-1][1] == s1[0][1]:
|
|
s1, s2 = s2, s1
|
|
elif s1[-1][1] == s2[0][1]:
|
|
pass
|
|
else:
|
|
return False
|
|
|
|
# If were still here s1 and s2 are 2 segments in the same poly-line.
|
|
s1.pop() # remove the last vert from s1
|
|
s1.extend(s2) # add segment 2 to segment 1
|
|
|
|
if s1[0][1] == s1[-1][1]: # remove endpoints double
|
|
s1.pop()
|
|
|
|
del s2[:] # Empty this segment s2 so we don't use it again.
|
|
return True
|
|
|
|
joining_segments = True
|
|
while joining_segments:
|
|
joining_segments = False
|
|
segcount = len(loop_segments)
|
|
|
|
for j in range(segcount - 1, -1, -1): # reversed(range(segcount)):
|
|
seg_j = loop_segments[j]
|
|
if seg_j:
|
|
for k in range(j - 1, -1, -1): # reversed(range(j)):
|
|
if not seg_j:
|
|
break
|
|
seg_k = loop_segments[k]
|
|
|
|
if seg_k and join_seg(seg_j, seg_k):
|
|
joining_segments = True
|
|
|
|
loop_list = loop_segments
|
|
|
|
for verts in loop_list:
|
|
while verts and verts[0][1] == verts[-1][1]:
|
|
verts.pop()
|
|
|
|
loop_list = [verts for verts in loop_list if len(verts) > 2]
|
|
# DONE DEALING WITH LOOP FIXING
|
|
|
|
# vert mapping
|
|
vert_map = [None] * len(indices)
|
|
ii = 0
|
|
for verts in loop_list:
|
|
if len(verts) > 2:
|
|
for i, vert in enumerate(verts):
|
|
vert_map[i + ii] = vert[2]
|
|
ii += len(verts)
|
|
|
|
fill = tessellate_polygon([[v[0] for v in loop] for loop in loop_list])
|
|
# draw_loops(loop_list)
|
|
# raise Exception("done loop")
|
|
# map to original indices
|
|
fill = [[vert_map[i] for i in f] for f in fill]
|
|
|
|
if not fill:
|
|
if debug_print:
|
|
print('Warning Cannot scanfill, fallback on a triangle fan.')
|
|
fill = [[0, i - 1, i] for i in range(2, len(indices))]
|
|
else:
|
|
# Use real scan-fill.
|
|
# See if its flipped the wrong way.
|
|
flip = None
|
|
for fi in fill:
|
|
if flip is not None:
|
|
break
|
|
for i, vi in enumerate(fi):
|
|
if vi == 0 and fi[i - 1] == 1:
|
|
flip = False
|
|
break
|
|
elif vi == 1 and fi[i - 1] == 0:
|
|
flip = True
|
|
break
|
|
|
|
if not flip:
|
|
for i, fi in enumerate(fill):
|
|
fill[i] = tuple(reversed(fi))
|
|
|
|
return fill
|
|
|
|
|
|
def triangle_random_points(num_points, loop_triangles):
|
|
"""
|
|
Generates a list of random points over mesh loop triangles.
|
|
|
|
:arg num_points: The number of random points to generate on each triangle.
|
|
:type num_points: int
|
|
:arg loop_triangles: Sequence of the triangles to generate points on.
|
|
:type loop_triangles: Sequence[:class:`bpy.types.MeshLoopTriangle`]
|
|
:return: List of random points over all triangles.
|
|
:rtype: list[:class:`mathutils.Vector`]
|
|
"""
|
|
|
|
from random import random
|
|
|
|
# For each triangle, generate the required number of random points
|
|
sampled_points = [None] * (num_points * len(loop_triangles))
|
|
for i, lt in enumerate(loop_triangles):
|
|
# Get triangle vertex coordinates
|
|
verts = lt.id_data.vertices
|
|
ltv = lt.vertices[:]
|
|
tv = (verts[ltv[0]].co, verts[ltv[1]].co, verts[ltv[2]].co)
|
|
|
|
for k in range(num_points):
|
|
u1 = random()
|
|
u2 = random()
|
|
u_tot = u1 + u2
|
|
|
|
if u_tot > 1:
|
|
u1 = 1.0 - u1
|
|
u2 = 1.0 - u2
|
|
|
|
side1 = tv[1] - tv[0]
|
|
side2 = tv[2] - tv[0]
|
|
|
|
p = tv[0] + u1 * side1 + u2 * side2
|
|
|
|
sampled_points[num_points * i + k] = p
|
|
|
|
return sampled_points
|