def branches_splitting_flipped_faces(self): """Add new branches to fix the problem of polyline patches that would form flipped faces in the decomposition mesh. Returns ------- new_branches : list List of polylines as list of point XYZ-coordinates. """ new_branches = [] centre_to_fkey = { geometric_key(trimesh_face_circle(self, fkey)[0]): fkey for fkey in self.faces() } # compute total rotation of polyline for polyline in self.branches_singularity_to_singularity(): angles = [ angle_vectors_signed(subtract_vectors(v, u), subtract_vectors(w, v), [0., 0., 1.]) for u, v, w in window(polyline, n=3) ] # subdivide once per angle limit in rotation if abs(sum(angles)) > self.flip_angle_limit: # the step between subdivision points in polylines (+ 2 for the extremities, which will be discarded) alone = len(self.compas_singular_faces()) == 0 n = floor(abs(sum(angles)) / self.flip_angle_limit) + 1 step = int(floor(len(polyline) / n)) # add new branches from corresponding face in Delaunay mesh seams = polyline[::step] if polyline[-1] != seams[-1]: if len(seams) == n + 1: del seams[-1] seams.append(polyline[-1]) if alone: seams = seams[0:-1] else: seams = seams[1:-1] for point in seams: fkey = centre_to_fkey[geometric_key(point)] for edge in self.face_halfedges(fkey): if not self.is_edge_on_boundary(*edge): new_branches += [[ trimesh_face_circle(self, fkey)[0], self.vertex_coordinates(vkey) ] for vkey in edge] break return new_branches
def lines(self): """Get the lines forming the topological skeleton, i.e. the lines connecting the circumcentres of adjacent faces. Returns ------- list List of lines as tuples of pairs XYZ-coordinates. """ return [(trimesh_face_circle(self, fkey)[0], trimesh_face_circle(self, nbr)[0]) for fkey in self.faces() for nbr in self.face_neighbors(fkey) if fkey < nbr and geometric_key(trimesh_face_circle(self, fkey)[0]) != geometric_key(trimesh_face_circle(self, nbr)[0])]
def branches_singularity_to_boundary(self): """Get new branch polylines between singularities and boundaries, at the location fo the split vertices. Not part of the topological skeleton. Returns ------- list List of polylines as list of point XYZ-coordinates. """ return [[trimesh_face_circle(self, fkey)[0], self.vertex_coordinates(vkey)] for fkey in self.compas_singular_faces() for vkey in self.face_vertices(fkey)]
def boundary_triangulation(outer_boundary, inner_boundaries, polyline_features = [], point_features = [], src='numpy_rpc'): """Generate Delaunay triangulation between a planar outer boundary and planar inner boundaries. All vertices lie the boundaries. Parameters ---------- outer_boundary : list Planar outer boundary as list of vertex coordinates. inner_boundaries : list List of planar inner boundaries as lists of vertex coordinates. polyline_features : list List of planar polyline_features as lists of vertex coordinates. point_features : list List of planar point_features as lists of vertex coordinates. src : str Source of Delaunay triangulation. Default is NumPy via RPC. Returns ------- delaunay_mesh : Mesh The Delaunay mesh. """ # generate planar Delaunay triangulation vertices = [pt for boundary in [outer_boundary] + inner_boundaries + polyline_features for pt in boundary] + point_features if src == 'numpy_rpc': faces = delaunay_numpy_rpc(vertices) elif src == 'numpy': faces = delaunay_numpy(vertices) else: delaunay_compas(vertices) delaunay_mesh = Mesh.from_vertices_and_faces(vertices, faces) # delete false faces with aligned vertices for fkey in list(delaunay_mesh.faces()): a, b, c = [delaunay_mesh.vertex_coordinates(vkey) for vkey in delaunay_mesh.face_vertices(fkey)] ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) if length_vector(cross_vectors(ab, ac)) == 0: delaunay_mesh.delete_face(fkey) # delete faces outisde the borders for fkey in list(delaunay_mesh.faces()): centre = trimesh_face_circle(delaunay_mesh, fkey)[0] if not is_point_in_polygon_xy(centre, outer_boundary) or any([is_point_in_polygon_xy(centre, inner_boundary) for inner_boundary in inner_boundaries]): delaunay_mesh.delete_face(fkey) # topological cut along the feature polylines through unwelding vertex_map = {geometric_key(delaunay_mesh.vertex_coordinates(vkey)): vkey for vkey in delaunay_mesh.vertices()} edges = [edge for polyline in polyline_features for edge in pairwise([vertex_map[geometric_key(point)] for point in polyline])] mesh_unweld_edges(delaunay_mesh, edges) return delaunay_mesh
def branches_singularity_to_singularity(self): """Get the branch polylines of the topological skeleton between singularities only, not corners. Returns ------- list List of polylines as list of point XYZ-coordinates. """ map_corners = [geometric_key(trimesh_face_circle(self, corner)[0]) for corner in self.corner_faces()] return [branch for branch in self.branches() if geometric_key(branch[0]) not in map_corners and geometric_key(branch[-1]) not in map_corners]
def branches_splitting_collapsed_boundaries(self): """Add new branches to fix the problem of boundaries with less than three splits that would be collapsed in the decomposition mesh. Returns ------- new_branches : list List of polylines as list of point XYZ-coordinates. """ new_branches = [] all_splits = set( list(self.corner_vertices()) + list(self.split_vertices())) for polyedge in [bdry + bdry[:1] for bdry in self.boundaries()]: splits = set([vkey for vkey in polyedge if vkey in all_splits]) new_splits = [] if len(splits) == 0: new_splits += [ vkey for vkey in list( itemgetter(0, int(floor(len(polyedge) / 3)), int(floor(len(polyedge) * 2 / 3)))(polyedge)) ] elif len(splits) == 1: i = polyedge.index(splits[0]) new_splits += list( itemgetter(i - int(floor(len(polyedge) * 2 / 3)), i - int(floor(len(polyedge) / 3)))(polyedge)) elif len(splits) == 2: one, two = list_split( polyedge, [polyedge.index(vkey) for vkey in splits]) half = one if len(one) > len(two) else two new_splits.append(half[int(floor(len(half) / 2))]) for vkey in new_splits: fkey = list(self.vertex_faces(vkey))[0] for edge in self.face_halfedges(fkey): if vkey in edge and not self.is_edge_on_boundary(*edge): new_branches += [[ trimesh_face_circle(self, fkey)[0], self.vertex_coordinates(vkey_2) ] for vkey_2 in edge] all_splits.update(edge) break return new_branches
def compas_singular_points(self): """Get the XYZ-coordinates of the compas_singular points of the topological skeleton, i.e. the face circumcentre of the compas_singular faces. Returns ------- list List of point XYZ-coordinates. """ return [ trimesh_face_circle(self, fkey)[0] for fkey in self.compas_singular_faces() ]
def branches_splitting_boundary_kinks(self): """Add new branches to fix the problem of boundary kinks not marked by the skeleton Due to a low density that did not spot the change of curvature at the kink. Does not modify the singularites on the contrarty to increasing the density. Returns ------- new_branches : list List of polylines as list of point XYZ-coordinates. """ new_branches = [] compas_singular_faces = set(self.compas_singular_faces()) for boundary in self.boundaries(): angles = {(u, v, w): angle_vectors( subtract_vectors(self.vertex_coordinates(v), self.vertex_coordinates(u)), subtract_vectors(self.vertex_coordinates(w), self.vertex_coordinates(v))) for u, v, w in window(boundary + boundary[:2], n=3)} for u, v, w, x, y in list(window(boundary + boundary[:4], n=5)): # check if not a corner if self.vertex_degree(w) == 2: continue angle = angles[(v, w, x)] adjacent_angles = (angles[(u, v, w)] + angles[(w, x, y)]) / 2 if angle - adjacent_angles > self.relative_kink_angle_limit: # check if not already marked via an adjacent compas_singular face if all([ fkey not in compas_singular_faces for fkey in self.vertex_faces(w) ]): fkeys = list(self.vertex_faces(w, ordered=True)) fkey = fkeys[int(floor(len(fkeys) / 2))] for edge in self.face_halfedges(fkey): if w in edge and not self.is_edge_on_boundary( *edge): new_branches += [[ trimesh_face_circle(self, fkey)[0], self.vertex_coordinates(vkey) ] for vkey in edge] break return new_branches