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
def face_skewness(self, fkey): """Face skewness as the maximum absolute angular deviation from the ideal polygon angle. Parameters ---------- fkey : Key The face key. Returns ------- float The skewness. References ---------- .. [1] Wikipedia. *Types of mesh*. Available at: https://en.wikipedia.org/wiki/Types_of_mesh. """ ideal_angle = 180 * (1 - 2 / float(len(self.face_vertices(fkey)))) angles = [] vertices = self.face_vertices(fkey) for u, v, w in window(vertices + vertices[:2], n=3): o = self.vertex_coordinates(v) a = self.vertex_coordinates(u) b = self.vertex_coordinates(w) angle = angle_points(o, a, b, deg=True) angles.append(angle) return max((max(angles) - ideal_angle) / (180 - ideal_angle), (ideal_angle - min(angles)) / ideal_angle)
def kagome_polyedge_weaving(self): mesh = self.kagome edge_to_polyedge_index = {} for i, polyedge in enumerate(self.kagome_polyedge_data): for u, v in pairwise(polyedge): edge_to_polyedge_index[(u, v)] = i edge_to_polyedge_index[(v, u)] = i vertex_to_polyege_offset = {vkey: {} for vkey in mesh.vertices()} for fkey in mesh.faces(): if len(mesh.face_vertices(fkey)) == 3: for u, v, w in window(mesh.face_vertices(fkey) + mesh.face_vertices(fkey)[:2], n=3): vertex_to_polyege_offset[v].update({ edge_to_polyedge_index[(u, v)]: +1, edge_to_polyedge_index[(v, w)]: -1 }) else: for u, v, w in window(mesh.face_vertices(fkey) + mesh.face_vertices(fkey)[:2], n=3): vertex_to_polyege_offset[v].update({ edge_to_polyedge_index[(u, v)]: -1, edge_to_polyedge_index[(v, w)]: +1 }) polyedge_weave = [] for i, polyedge in enumerate(self.kagome_polyedge_data): polyedge_weave.append( [vertex_to_polyege_offset[vkey][i] for vkey in polyedge]) return polyedge_weave
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 flatness(vertices, faces, maxdev=0.02): """Compute mesh flatness per face. Parameters ---------- vertices : list The vertex coordinates. faces : list The face vertices. maxdev : float, optional A maximum value for the allowed deviation from flatness. Default is ``0.02``. Returns ------- dict For each face, a deviation from *flatness*. Notes ----- The "flatness" of a face is expressed as the ratio of the distance between the diagonals to the average edge length. For the fabrication of glass panels, for example, ``0.02`` could be a reasonable maximum value. Warning ------- This function only works as expected for quadrilateral faces. See Also -------- * :func:`compas.geometry.mesh_flatness` """ dev = [] for face in faces: points = [vertices[index] for index in face] lengths = [ distance_point_point(a, b) for a, b in window(points + points[0:1], 2) ] l = sum(lengths) / len(lengths) d = distance_line_line((points[0], points[2]), (points[1], points[3])) dev.append((d / l) / maxdev) return dev
def is_polygon_convex(polygon): """Determine if a polygon is convex. Parameters ---------- polygon : sequence of sequence of floats The XYZ coordinates of the corners of the polygon. Notes ----- Use this function for *spatial* polygons. If the polygon is in a horizontal plane, use :func:`is_polygon_convex_xy` instead. Returns ------- bool ``True`` if the polygon is convex. ``False`` otherwise. See Also -------- is_polygon_convex_xy Examples -------- >>> polygon = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.4, 0.4, 0.0], [0.0, 1.0, 0.0]] >>> is_polygon_convex(polygon) False """ a = polygon[0] o = polygon[1] b = polygon[2] oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n0 = cross_vectors(oa, ob) for a, o, b in window(polygon + polygon[:2], 3): oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) if dot_vectors(n, n0) >= 0: continue else: return False return True
def mesh_flatness(mesh, maxdev=1.0): """Compute mesh flatness per face. Parameters ---------- mesh : Mesh A mesh object. maxdev : float, optional A maximum value for the allowed deviation from flatness. Default is ``1.0``. Returns ------- dict For each face, a deviation from *flatness*. Notes ----- The "flatness" of a face is expressed as the ratio of the distance between the diagonals to the average edge length. For the fabrication of glass panels, for example, ``0.02`` could be a reasonable maximum value. Warnings -------- This function only works as expected for quadrilateral faces. """ dev = [] for fkey in mesh.faces(): points = mesh.face_coordinates(fkey) if len(points) == 3: dev.append(0.0) else: lengths = [ distance_point_point(a, b) for a, b in window(points + points[0:1], 2) ] length = sum(lengths) / len(lengths) d = distance_line_line((points[0], points[2]), (points[1], points[3])) dev.append((d / length) / maxdev) return dev
def is_polygon_convex(polygon): """Determine if a polygon is convex. Parameters ---------- polygon : sequence[point] | :class:`compas.geometry.Polygon` A polygon. Returns ------- bool True if the polygon is convex. False otherwise. Notes ----- Use this function for *spatial* polygons. If the polygon is in a horizontal plane, use :func:`is_polygon_convex_xy` instead. Examples -------- >>> polygon = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.4, 0.4, 0.0], [0.0, 1.0, 0.0]] >>> is_polygon_convex(polygon) False """ a = polygon[0] o = polygon[1] b = polygon[2] oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n0 = cross_vectors(oa, ob) for a, o, b in window(polygon + polygon[:2], 3): oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) if dot_vectors(n, n0) >= 0: continue else: return False return True
def mesh_flatness(mesh): """Compute mesh flatness per face. The "flatness" of a face is expressed as the average of the angles between the normal at each face corner and the normal of the best-fit plane. """ dev = {} for fkey in mesh.faces(): points = mesh.face_coordinates(fkey) base, normal = bestfit_plane_from_points(points) angles = [] for a, b, c in window(points + points[0:2], 3): u = subtract_vectors(a, b) v = subtract_vectors(c, b) n = cross_vectors(u, v) if dot_vectors(n, normal) > 0: angle = angle_smallest_vectors(n, normal) else: angle = angle_smallest_vectors(n, scale_vector(normal, -1)) angles.append(angle) dev[fkey] = sum(angles) / len(angles) return dev
def face_corners(self, fkey): vertices = self.face_vertices(fkey) return window(vertices + vertices[0:2], 3)
controlpoints = [ Point(0, 0, 0), Point(4, 2.5, 0), Point(6, -2.5, 0), Point(10, 0, 0) ] controlpoly = Polyline(controlpoints) curve = Bezier(controlpoints) poly = Polyline(curve.locus()) poly1 = Polyline(offset_polyline(poly, +0.15)) poly2 = Polyline(offset_polyline(poly, -0.15)) points = [poly.point(t) for t in linspace(0, 1, 20)] tangents = [(c - a).unitized() for a, b, c in window(points, 3) if a and c] normals = [Vector(0, 0, 1).cross(t) for t in tangents] lines = [[point, point + normal] for point, normal in zip(points[1:-1], normals)] points1 = [intersection_line_polyline(line, poly1) for line in lines] points2 = [intersection_line_polyline(line, poly2) for line in lines] frames = [] for a, b in pairwise(points[1:-1]): p = (a + b) * 0.5 t = (b - a).unitized() n = Vector(0, 0, 1).cross(t) frame = Frame(p, t, n) frames.append(frame)
def center_of_mass_polyhedron(polyhedron): """Compute the center of mass of a polyhedron. Parameters ---------- polyhedron : tuple The coordinates of the vertices, and the indices of the vertices forming the faces. Returns ------- tuple XYZ coordinates of the center of mass. Examples -------- >>> from compas.geometry import Polyhedron >>> p = Polyhedron.generate(6) >>> center_of_mass_polyhedron((p.vertices, p.faces)) (-4.206480876464043e-17, -4.206480876464043e-17, -4.206480876464043e-17) """ vertices, faces = polyhedron V = 0 x = 0.0 y = 0.0 z = 0.0 ex = [1.0, 0.0, 0.0] ey = [0.0, 1.0, 0.0] ez = [0.0, 0.0, 1.0] for face in faces: if len(face) == 3: triangles = [face] else: centroid = centroid_points([vertices[index] for index in face]) vertices.append(centroid) w = len(vertices) - 1 triangles = [[u, v, w] for u, v in window(face + face[0:1], 2)] for triangle in triangles: a = vertices[triangle[0]] b = vertices[triangle[1]] c = vertices[triangle[2]] ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) V += dot_vectors(a, n) nx = dot_vectors(n, ex) ny = dot_vectors(n, ey) nz = dot_vectors(n, ez) for j in (-1, 0, 1): ab = add_vectors(vertices[triangle[j]], vertices[triangle[j + 1]]) x += nx * dot_vectors(ab, ex)**2 y += ny * dot_vectors(ab, ey)**2 z += nz * dot_vectors(ab, ez)**2 if V < 1e-9: V = 0.0 d = 1.0 / 48.0 else: V = V / 6.0 d = 1.0 / 48.0 / V x *= d y *= d z *= d return x, y, z