import numpy as np
from ddg.datastructures.halfedge import surface
from ddg.datastructures.indexedfaceset import ifs as IFS
[docs]def diags_from_faces(faces):
"""
Calculates diagonals of the faces of a quadrilateral mesh.
Parameters
----------
faces : np.ndarray
Array of faces
In case only one face exists, it should be passed as a 2D array
Returns
-------
tuple
a tuple of 2d array of two sets of diagonals of the quads
"""
if not isinstance(faces, np.ndarray):
raise ValueError("The faces must be numpy arrays")
if faces.ndim != 2:
raise ValueError(
"The face array must be a 2D numpy array. Even only one face exists, it"
" must be passed as a 2D array"
)
if faces.shape[1] != 4:
raise ValueError("the function can only be called for quadrilateral mesh")
diag1 = np.column_stack((faces[:, 0], faces[:, 2]))
diag2 = np.column_stack((faces[:, 1], faces[:, 3]))
return diag1, diag2
[docs]def is_manifold(ifs):
"""
Returns whether every edge of the non oriented indexed face set is contained in
at most two faces.
"""
# is_manifold for oriented face sets gets checked in construction and
# prevented if neccessary.
facesPerEdge = dict()
for f in ifs.face_list():
for e in ifs.face_boundary(f):
e = tuple(sorted(e))
if e in facesPerEdge:
facesPerEdge[e] += 1
else:
facesPerEdge[e] = 1
if facesPerEdge[e] > 2:
return False
return True
[docs]def orient(src_ifs, face_map=dict()):
"""
Try to orient a given indexed face set. Raises Value Error
if the given set is not orientable.
"""
if not is_manifold(src_ifs):
raise NonManifoldException()
# 0 : not seen
# 1 : in the queue
# 2 : done
unseen_src_faces = {tuple(f): 0 for f in src_ifs.face_list()}
n_visited_faces = 0
oriented_ifs = IFS.OrientedIndexedFaceSet([])
oriented_faces_queue = [IFS.OrientedFace(src_ifs.face_list()[0], orientation=1)]
while n_visited_faces < src_ifs.number_of_faces():
if len(oriented_faces_queue) == 0:
next_face = next(
iter(
filter(lambda x: unseen_src_faces[x] == 0, unseen_src_faces.keys())
)
)
oriented_faces_queue.append(
IFS.OrientedFace(tuple(next_face), orientation=1)
)
oriented_face = oriented_faces_queue.pop(0)
unseen_src_faces[oriented_face.get_tuple()] = 2
n_visited_faces += 1
try:
oriented_ifs.add_face(oriented_face.get_oriented_tuple())
neighboring_faces = [
n_face
for n_face in src_ifs.neighboring_faces_with_orientation(
oriented_face.get_tuple()
)
if not isinstance(n_face, IFS.NoneFace)
]
for neighboring_face in [
nf for nf in neighboring_faces if unseen_src_faces[nf.get_tuple()] == 0
]:
neighboring_face.set_orientation(
-1
* neighboring_face.get_orientation()
* oriented_face.get_orientation()
)
oriented_faces_queue.append(neighboring_face)
unseen_src_faces[neighboring_face.get_tuple()] = 1
face_map[oriented_face.get_oriented_tuple()] = oriented_face.get_tuple()
except ValueError:
raise NonOrientableException()
return oriented_ifs
[docs]def indexed_face_set_to_surface(
ifs, vertex_index_attribute="ifs_index", face_index_attribute="ifs_face_index"
):
"""
Convert an indexed face set to a half edge data structure.
If the indexed face set can not be converted an empty half
edge data structure is returned.
"""
hds = surface.Surface()
if vertex_index_attribute is not None:
hds.verts.add_attribute(vertex_index_attribute)
if face_index_attribute is not None:
hds.faces.add_attribute(face_index_attribute)
faceMap = dict()
orientedIFS = orient(ifs, faceMap)
vertices = set([v for l in orientedIFS.face_list() for v in l])
vertexMap = dict()
for v in vertices:
vertex = hds.verts()
vertexMap[v] = vertex
if vertex_index_attribute is not None:
setattr(vertex, vertex_index_attribute, v)
original_face_index_map = {f: i for i, f in enumerate(ifs.face_list())}
edgeMap = dict()
for f in orientedIFS.face_list():
edges = []
face = hds.faces()
original_face = faceMap[tuple(f)]
f_index = original_face_index_map[original_face]
if face_index_attribute is not None:
setattr(face, face_index_attribute, f_index)
for e in ifs.face_boundary(f):
edge = hds.edges()
edge.face = face
edgeMap[e] = edge
edges.append(edge)
head = vertexMap[e[1]]
edge.head = head
if head.edge is None:
head.edge = edge
if face.edge is None:
face.edge = edge
for i in range(len(edges)):
edges[i].nex = edges[(i + 1) % len(edges)]
edges[(i + 1) % len(edges)].pre = edges[i]
boundaryEdges = []
for eTuple in list(edgeMap):
edge = edgeMap[eTuple]
if edge.opp is not None:
continue
else:
eOppTuple = tuple(reversed(eTuple))
if eOppTuple in edgeMap:
oppEdge = edgeMap[eOppTuple]
oppEdge.opp = edge
edge.opp = oppEdge
else:
oppEdge = hds.edges()
oppEdge.head = vertexMap[eOppTuple[1]]
boundaryEdges.append(eOppTuple)
edgeMap[eOppTuple] = oppEdge
oppEdge.opp = edge
edge.opp = oppEdge
for eTuple in boundaryEdges:
nextEdgeTuple = _find_next_tuple(eTuple, boundaryEdges)
edgeMap[eTuple].nex = edgeMap[nextEdgeTuple]
edgeMap[nextEdgeTuple].pre = edgeMap[eTuple]
return hds
[docs]def face_boundary(face):
return tuple(zip(face, tuple(list(face[1:]) + [face[0]])))
def _find_next_tuple(edge, edgeList):
targetIndex = edge[1]
nextEdges = []
for e in edgeList:
if e[0] == targetIndex:
nextEdges.append(e)
if len(nextEdges) != 1:
raise BoundaryException(
"Head of edge "
+ str(edge)
+ " does not meet tail of ONE edge in "
+ str(edgeList)
)
else:
return nextEdges[0]
[docs]class BoundaryException(Exception):
pass
[docs]class NonManifoldException(Exception):
pass
[docs]class NonOrientableException(Exception):
pass