import numpy as np
import ddg
from ddg.indexedfaceset import _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 = ddg.halfedge.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]def dualize(ifs, link_vertex_attributes=True, link_face_attributes=True):
"""
Constructs indexed face set with dual combinatorics.
For the dual combinatorics vertices and faces are interchanged.
The enumeration of vertices and faces corresponds to the enumeration in the original
surfaces.
Parameters
----------
ifs : ddg.indexedfaceset.IndexedFaceSet
Discrete surface to be dualized.
link_vertex_attributes : bool (default=True)
If `True`, vertix attributes of the original ifs are set as face attributes of
the dual ifs
link_face_attributes : bool (default=True)
If `True`, face attributes of the original ifs are set as vertex attributes of
the dual ifs
Returns
-------
ddg.indexedfaceset.IndexedFaceSet
The dual surface.
"""
he = indexed_face_set_to_surface(
ifs, vertex_index_attribute="ifs_index", face_index_attribute="ifs_face_index"
)
sorted_vertices = sorted(
ddg.halfedge.interior_vertices(he), key=lambda x: x.ifs_index
)
dual_faces = [
[e.face.ifs_face_index for e in ddg.halfedge.in_edges(v)]
for v in sorted_vertices
]
dual_ifs = ddg.indexedfaceset.IndexedFaceSet(dual_faces)
if link_vertex_attributes:
dual_ifs.face_attributes = ifs.vertex_attributes
if link_face_attributes:
dual_ifs.vertex_attributes = ifs.face_attributes
return dual_ifs
[docs]def mean_values_on_faces(ifs, attr="co"):
"""
Creates a new attribute on faces with mean values of the attribute on vertices.
Parameters
----------
ifs : ddg.indexedfaceset.IndexedFaceSet
Indexed face set to create the new attribute for.
attr : str
Name of the vertex attribute, which is also used as name for the new face
attribute.
"""
# v is of typer np.int64 and has to be cast to int for get_attribute
mean_values = [
np.mean([ifs.get_attribute(attr, "verts", int(v)) for v in f], axis=0)
for f in ifs.faces
]
ifs.set_attribute(attr, "faces", mean_values)
[docs]class BoundaryException(Exception):
pass
[docs]class NonManifoldException(Exception):
pass
[docs]class NonOrientableException(Exception):
pass