def intersection_polyline_box_xy(polyline, box, tol=1e-6): """Compute the intersection between a polyline and a box in the XY plane. Parameters ---------- polyline : sequence[point] | :class:`compas.geometry.Polyline` A polyline defined by a sequence of points, with at least XY coordinates. box : [point, point, point, point] A box defined by a sequence of 4 points, with at least XY coordinates. tol : float, optional A tolerance value for point comparison. Returns ------- list[[float, float, 0.0]] A list of intersection points. """ precision = compas.PRECISION compas.set_precision(tol) points = [] for side in pairwise(box + box[:1]): for segment in pairwise(polyline): x = intersection_segment_segment_xy(side, segment, tol=tol) if x: points.append(x) points = {geometric_key(point): point for point in points} compas.PRECISION = precision return list(points.values())
def _face_adjacency(xyz, faces, nmax=10, radius=2.0): points = [centroid_points([xyz[index] for index in face]) for face in faces] tree = KDTree(points) closest = [tree.nearest_neighbors(point, nmax) for point in points] closest = [[index for _, index, _ in nnbrs] for nnbrs in closest] adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() nnbrs = set(closest[face]) for u, v in pairwise(vertices + vertices[0:1]): for nbr in nnbrs: if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def intersection_polyline_box_xy(polyline, box, tol=1e-6): """Compute the intersection between a polyline and a box in the XY plane. Parameters ---------- polyline : list of points or :class:`compas.geometry.Polyline` box : list of 4 points tol : float, optional A tolerance value for point comparison. Default is ``1e-6``. Returns ------- list A list of intersection points. """ precision = compas.PRECISION compas.set_precision(tol) points = [] for side in pairwise(box + box[:1]): for segment in pairwise(polyline): x = intersection_segment_segment_xy(side, segment, tol=tol) if x: points.append(x) points = {geometric_key(point): point for point in points} compas.PRECISION = precision return list(points.values())
def face_adjacency(xyz, faces): f = len(faces) if f > 100: return _face_adjacency(xyz, faces) adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() for u, v in pairwise(vertices + vertices[0:1]): for nbr, _ in enumerate(faces): if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def _face_adjacency(xyz, faces, nmax=10, radius=2.0): points = [centroid_points([xyz[index] for index in face]) for face in faces] k = min(len(faces), nmax) tree = cKDTree(points) _, closest = tree.query(points, k=k, n_jobs=-1) adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() nnbrs = set(closest[face]) for u, v in pairwise(vertices + vertices[0:1]): for nbr in nnbrs: if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def draw_box(bbox, color, layer): lines = [] for a, b in pairwise(bbox[:4] + bbox[:1]): lines.append({'start': a, 'end': b, 'color': color}) for a, b in pairwise(bbox[4:] + bbox[4:5]): lines.append({'start': a, 'end': b, 'color': color}) for a, b in zip(bbox[:4], bbox[4:]): lines.append({'start': a, 'end': b, 'color': color}) compas_rhino.draw_lines(lines, layer=layer, clear=False)
def face_adjacency(xyz, faces): """Construct an adjacency dictionary of the given faces, assuming that the faces have arbitrary orientation. Parameters ---------- xyz : sequence[[float, float, float] | :class:`compas.geometry.Point`] The coordinates of the face vertices. faces : sequence[sequence[int]] A list of faces with each face defined by a list of indices into the list of xyz coordinates. Returns ------- dict[int, list[int]] For every face a list of neighbouring faces. Notes ----- If the number of faces is larger than one hundred (100), this function uses Rhino's RTree for limiting the search for neighbours to the immediate surroundings of any given face. Examples -------- >>> vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 1.0]] >>> faces = [[0, 1, 2], [0, 3, 2]] >>> face_adjacency(vertices, faces) {0: [1], 1: [0]} """ f = len(faces) if f > 100: return _face_adjacency(xyz, faces) adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() for u, v in pairwise(vertices + vertices[0:1]): for nbr, _ in enumerate(faces): if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def draw_cloud(cloud, bbox, color, layer): points = [{'pos': xyz, 'color': color} for xyz in cloud] lines = [] for a, b in pairwise(bbox[:4] + bbox[:1]): lines.append({'start': a, 'end': b, 'color': color}) for a, b in pairwise(bbox[4:] + bbox[4:5]): lines.append({'start': a, 'end': b, 'color': color}) for a, b in zip(bbox[:4], bbox[4:]): lines.append({'start': a, 'end': b, 'color': color}) compas_rhino.draw_points(points, layer=layer, clear=True) compas_rhino.draw_lines(lines, layer=layer, clear=False)
def face_adjacency_rhino(xyz, faces): """Construct an adjacency dictionary of the given faces, assuming that the faces have arbitrary orientation. Parameters ---------- xyz : list The coordinates of the face vertices. faces : list The indices of the face vertices in the coordinates list. Returns ------- dict For every face a list of neighbouring faces. Examples -------- >>> vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 1.0]] >>> faces = [[0, 1, 2], [0, 3, 2]] >>> face_adjacency_rhino(vertices, faces) # doctest: +SKIP {0: [1], 1: [0]} Notes ----- This function uses Rhino's RTree for limiting the search for neighbours to the immediate surroundings of any given face if the number of faces is greater than 100. """ f = len(faces) if f > 100: return _face_adjacency(xyz, faces) adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() for u, v in pairwise(vertices + vertices[0:1]): for nbr, _ in enumerate(faces): if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def _face_adjacency(xyz, faces, nmax=10, radius=2.0): """""" points = [ centroid_points([xyz[index] for index in face]) for face in faces ] tree = RTree() for i, point in enumerate(points): tree.Insert(Point3d(*point), i) def callback(sender, e): data = e.Tag data.append(e.Id) closest = [] for i, point in enumerate(points): sphere = Sphere(Point3d(*point), radius) data = [] tree.Search(sphere, callback, data) closest.append(data) adjacency = {} for face, vertices in enumerate(faces): nbrs = [] found = set() nnbrs = set(closest[face]) for u, v in pairwise(vertices + vertices[0:1]): for nbr in nnbrs: if nbr == face: continue if nbr in found: continue for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if v == a and u == b: nbrs.append(nbr) found.add(nbr) break for a, b in pairwise(faces[nbr] + faces[nbr][0:1]): if u == a and v == b: nbrs.append(nbr) found.add(nbr) break adjacency[face] = nbrs return adjacency
def draw_mesh(self, color=None, disjoint=False): """Draw the mesh as a consolidated RhinoMesh. Notes ----- The mesh should be a valid Rhino Mesh object, which means it should have only triangular or quadrilateral faces. """ key_index = self.mesh.key_index() vertices = self.mesh.get_vertices_attributes('xyz') faces = [[key_index[key] for key in self.mesh.face_vertices(fkey)] for fkey in self.mesh.faces()] new_faces = [] for face in faces: f = len(face) if f == 3: new_faces.append(face + face[-1:]) elif f == 4: new_faces.append(face) elif f > 4: centroid = len(vertices) vertices.append(centroid_polygon([vertices[index] for index in face])) for a, b in pairwise(face + face[0:1]): new_faces.append([centroid, a, b, b]) else: continue layer = self.layer name = "{}.mesh".format(self.mesh.name) return compas_rhino.draw_mesh(vertices, new_faces, layer=layer, name=name, color=color, disjoint=disjoint)
def face_flatness(self, fkey, maxdev=0.02): """Compute the flatness of the mesh face. Parameters ---------- fkey : int The identifier of the face. maxdev : float, optional A maximum value for the allowed deviation from flatness. Default is ``0.02``. Returns ------- float The flatness. Notes ----- Flatness is computed as the ratio of the distance between the diagonals of the face to the average edge length. A practical limit on this value realted to manufacturing is 0.02 (2%). Warnings -------- This method only makes sense for quadrilateral faces. """ vertices = self.face_vertices(fkey) f = len(vertices) points = self.vertices_attributes('xyz', keys=vertices) lengths = [distance_point_point(a, b) for a, b in pairwise(points + points[:1])] length = sum(lengths) / f d = distance_line_line((points[0], points[2]), (points[1], points[3])) return (d / length) / maxdev
def intersection_polyline_plane(polyline, plane, expected_number_of_intersections=None, tol=1e-6): """Calculate the intersection point of a plane with a polyline. Reduce expected_number_of_intersections to speed up. Parameters ---------- polyline : :class:`compas.geometry.Polyline` or sequence of points Polyline to test intersection. plane : :class:`compas.geometry.Plane` or point and vector Plane to compute intersection. expected_number_of_intersections : integer, optional Number of useful or expected intersections. Default is the number of lines conforming the polyline. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- list of points """ if not expected_number_of_intersections: expected_number_of_intersections = len(polyline) intersections = [] for segment in pairwise(polyline): if len(intersections) == expected_number_of_intersections: break point = intersection_segment_plane(segment, plane, tol) if point: intersections.append(point) return intersections
def mesh_weld(mesh, precision=None, cls=None): """Weld vertices of a mesh within some precision distance. Parameters ---------- mesh : Mesh A mesh. precision: str (None) Tolerance distance for welding. cls : type (None) Type of the welded mesh. This defaults to the type of the first mesh in the list. Returns ------- mesh The welded mesh. """ if cls is None: cls = type(mesh) geo = geometric_key key_xyz = {key: mesh.vertex_coordinates(key) for key in mesh.vertices()} gkey_key = {geo(xyz, precision): key for key, xyz in key_xyz.items()} gkey_index = {gkey: index for index, gkey in enumerate(gkey_key)} vertices = [key_xyz[key] for gkey, key in gkey_key.items()] faces = [[gkey_index[geo(key_xyz[key], precision)] for key in mesh.face_vertices(fkey)] for fkey in mesh.faces()] faces[:] = [[u for u, v in pairwise(face + face[:1]) if u != v] for face in faces] return cls.from_vertices_and_faces(vertices, faces)
def offset_segments(point_list, distances, normal): """ """ segments = [] for line, distance in zip(pairwise(point_list), distances): segments.append(offset_line(line, distance, normal)) return segments
def offset_polyline(polyline, distance, normal=[0.0, 0.0, 1.0], tol=1e-6): """Offset a polyline by a distance. Parameters ---------- polyline : list of point The XYZ coordinates of the vertices of a polyline. distance : float or list of tuples of floats The offset distance as float. A single value determines a constant offset globally. Alternatively, pairs of local offset values per line segment can be used to create variable offsets. Distance > 0: offset to the "left", distance < 0: offset to the "right". normal : vector The normal of the offset plane. Returns ------- offset polyline : list of point The XYZ coordinates of the resulting polyline. """ if not is_item_iterable(distance): distance = [distance] distances = iterable_like(polyline, distance, distance[-1]) segments = offset_segments(polyline, distances, normal) offset = [segments[0][0]] for s1, s2 in pairwise(segments): point = intersect(s1, s2, tol) offset.append(point) offset.append(segments[-1][1]) return offset
def collect_polyedges(self): """Collect the polyedges accross four-valent vertices between boundaries and/or singularities and store it in the mesh data attributes. Parameters ---------- Returns ------- polyedges : list List of quad polyedges as list of vertices. """ edges = list(self.edges()) nb_polyedges = -1 while len(edges) > 0: nb_polyedges += 1 # collect new polyedge u0, v0 = edges.pop() polyedge = self.collect_polyedge(u0, v0) self.data['attributes']['polyedges'].update({nb_polyedges: polyedge}) # remove collected edges for u, v in pairwise(polyedge): if (u, v) in edges: edges.remove((u, v)) elif (v, u) in edges: edges.remove((v, u)) return self.polyedges(data=True)
def closest_point_on_polyline(polyline, c): """Closest point on polyline. If there are multiple closest points, the one from the first polyline segment is yielded. Parameters ---------- polyline: list List of polyline point coordinates. p: list Point coordinates. Returns ------- tuple The projected point coordinates and the distance from the input point. """ proj_p = None min_distance = None for a, b in pairwise(polyline): p, distance = closest_point_on_segment(a, b, c) if proj_p is None or min_distance > distance: proj_p = p min_distance = distance return proj_p, min_distance
def vertex_curvature(self, vkey): """Dimensionless vertex curvature. Parameters ---------- fkey : int The face key. Returns ------- float The dimensionless curvature. References ---------- Based on [1]_ .. [1] Botsch, Mario, et al. *Polygon mesh processing.* AK Peters/CRC Press, 2010. """ C = 0 for u, v in pairwise( self.vertex_neighbors(vkey, ordered=True) + self.vertex_neighbors(vkey, ordered=True)[:1]): C += angle_points(self.vertex_coordinates(vkey), self.vertex_coordinates(u), self.vertex_coordinates(v)) return 2 * pi - C
def closest_point_on_polyline_xy(point, polyline): """Compute closest point on a polyline to a given point, assuming they both lie in the XY-plane. Parameters ---------- point : sequence of float XY(Z) coordinates of a 2D or 3D point (Z will be ignored). polygon : sequence A sequence of XY(Z) coordinates of 2D or 3D points (Z will be ignored) representing the locations of the corners of a polygon. The vertices are assumed to be in order. The polygon is assumed to be closed: the first and last vertex in the sequence should not be the same. Returns ------- list XYZ coordinates of closest point (Z = 0.0). """ cloud = [] for segment in pairwise(polyline): cloud.append(closest_point_on_segment_xy(point, segment)) return closest_point_in_cloud_xy(point, cloud)[1]
def closest_point_on_polyline_xy(point, polyline): """Compute closest point on a polyline to a given point, assuming they both lie in the XY-plane. Parameters ---------- point : sequence of float XY(Z) coordinates of a 2D or 3D point (Z will be ignored). polyline : list of points or :class:`compas.geometry.Polyline` A sequence of XY(Z) coordinates of 2D or 3D points (Z will be ignored) representing the locations of the corners of a polyline. The vertices are assumed to be in order. Returns ------- list XYZ coordinates of closest point (Z = 0.0). """ cloud = [] for segment in pairwise(polyline): cloud.append(closest_point_on_segment_xy(point, segment)) return closest_point_in_cloud_xy(point, cloud)[1]
def mesh_unweld_vertices(mesh, fkey, where=None): """Unweld a face of the mesh. Parameters ---------- mesh : Mesh A mesh object. fkey : hashable The identifier of a face. where : list (None) A list of vertices to unweld. Default is to unweld all vertices of the face. Examples -------- >>> """ face = [] vertices = mesh.face_vertices(fkey) if not where: where = vertices for u, v in pairwise(vertices + vertices[0:1]): if u in where: x, y, z = mesh.vertex_coordinates(u) u = mesh.add_vertex(x=x, y=y, z=z) if u in where or v in where: mesh.halfedge[v][u] = None face.append(u) mesh.add_face(face, fkey=fkey) return face
def edges_on_boundaries(self): """""" vertexgroups = self.vertices_on_boundaries() edgegroups = [] for vertices in vertexgroups: edgegroups.append(list(pairwise(vertices))) return edgegroups
def mesh(self, mesh): self._mesh = mesh key_index = mesh.key_index() xyz = mesh.vertices_attributes('xyz') faces = [] for fkey in mesh.faces(): fvertices = [key_index[key] for key in mesh.face_vertices(fkey)] f = len(fvertices) if f < 3: pass elif f == 3: faces.append(fvertices) elif f == 4: a, b, c, d = fvertices faces.append([a, b, c]) faces.append([c, d, a]) else: o = mesh.face_centroid(fkey) v = len(xyz) xyz.append(o) for a, b in pairwise(fvertices + fvertices[0:1]): faces.append([a, b, v]) self._xyz = xyz self._faces = faces
def kagome_polyedge_colouring(kagome): polyedges = kagome.polyedge_data edge_to_polyedge_index = {vkey: {} for vkey in kagome.vertices()} for i, polyedge in enumerate(polyedges): for u, v in pairwise(polyedge): edge_to_polyedge_index[u][v] = i edge_to_polyedge_index[v][u] = i vertices = [ centroid_points([kagome.vertex_coordinates(vkey) for vkey in polyedge]) for polyedge in polyedges ] edges = [] for idx, polyedge in enumerate(polyedges): for vkey in polyedge: for vkey_2 in kagome.vertex_neighbors(vkey): idx_2 = edge_to_polyedge_index[vkey][vkey_2] if idx_2 != idx and idx < idx_2 and (idx, idx_2) not in edges: edges.append((idx, idx_2)) polyedge_network = Network.from_nodes_and_edges(vertices, edges) key_to_colour = vertex_coloring(polyedge_network.adjacency) return [key_to_colour[key] for key in sorted(key_to_colour.keys())]
def draw(self, color=None): """Draw the mesh as a RhinoMesh. Parameters ---------- color : 3-tuple, optional RGB color components in integer format (0-255). Returns ------- :class:`Rhino.Geometry.Mesh` """ vertex_index = self.mesh.key_index() vertices = self.mesh.vertices_attributes('xyz') faces = [[ vertex_index[vertex] for vertex in self.mesh.face_vertices(face) ] for face in self.mesh.faces()] new_faces = [] for face in faces: f = len(face) if f == 3: new_faces.append(face + [face[-1]]) elif f == 4: new_faces.append(face) elif f > 4: centroid = len(vertices) vertices.append( centroid_polygon([vertices[index] for index in face])) for a, b in pairwise(face + face[0:1]): new_faces.append([centroid, a, b, b]) else: continue return compas_ghpython.draw_mesh(vertices, new_faces, color)
def lines(self): """list of :class:`compas.geometry.Line` : The lines of the polygon.""" if not self._lines: self._lines = [ Line(a, b) for a, b in pairwise(self.points + self.points[:1]) ] return self._lines
def my_edges_on_boundaries(mesh): vertexgroups = mesh.vertices_on_boundaries() edgegroups = [] for vertices in vertexgroups: edgegroups.append(list(pairwise(vertices + vertices[:1]))) return edgegroups
def clean_faces(faces): for face in faces: for u, v in pairwise(face + face[:1]): if u == v: face.remove(u) break
def intersection_line_box_xy(line, box, tol=1e-6): """Compute the intersection between a line and a box in the XY plane. Parameters ---------- line : list of 2 points or :class:`compas.geometry.Line` box : list of 4 points tol : float, optional A tolerance value for point comparison. Default is ``1e-6``. Returns ------- list A list of at most two intersection points. """ points = [] for segment in pairwise(box + box[:1]): x = intersection_line_segment_xy(line, segment, tol=tol) if x: points.append(x) if len(points) < 3: return points if len(points) == 3: a, b, c = points if allclose(a, b, tol=tol): return [a, c] if allclose(b, c, tol=tol): return [a, b] return [b, c]