Source code for ddg.indexedfaceset._utils

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]def transform_attribute(transform, ifs, cell_type, co="uv", attribute_name="f"): """Add a transformed attribute to an indexed face set. Parameters ---------- transform : Callable Function to transform the attribute with ifs : ddg.indexedfaceset.IndexedFaceSet Indexed face set with the given attribute cell_type : str Cell type on which the attribute is defined. Can be either 'verts', 'edges', or 'faces'. co : str (default = 'uv') Name of the attribute to use. attribute_name : str (default = 'f') Name of the transformed attribute """ match cell_type: case "verts": coordinates = ifs.vertex_attributes[co] case "edges": coordinates = ifs.edge_attributes[co] case "faces": coordinates = ifs.face_attributes[co] values = np.array([transform(i) for i in coordinates]) ifs.set_attribute(attribute_name, cell_type, values)
[docs]class BoundaryException(Exception): pass
[docs]class NonManifoldException(Exception): pass
[docs]class NonOrientableException(Exception): pass