def draw_circle(circle, color=None, n=100): (center, normal), radius = circle cx, cy, cz = center a, b, c = normal u = -1.0, 0.0, a v = 0.0, -1.0, b w = cross_vectors(u, v) uvw = [normalize_vector(u), normalize_vector(v), normalize_vector(w)] color = color if color else (1.0, 0.0, 0.0, 0.5) sector = 2 * pi / n glColor4f(*color) glBegin(GL_POLYGON) for i in range(n): a = i * sector x = radius * cos(a) y = radius * sin(a) z = 0 x, y, z = global_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd() glBegin(GL_POLYGON) for i in range(n): a = -i * sector x = radius * cos(a) y = radius * sin(a) z = 0 x, y, z = global_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd()
def bestfit_circle_numpy(points): """Fit a circle through a set of points. Parameters ---------- points : list XYZ coordinates of the points. Returns ------- tuple XYZ coordinates of the center of the circle, the normal vector of the local frame, and the radius of the circle. Notes ----- For more information see [1]_. References ---------- .. [1] Scipy. *Least squares circle*. Available at: http://scipy-cookbook.readthedocs.io/items/Least_Squares_Circle.html. Examples -------- .. code-block:: python # """ o, uvw, _ = pca_numpy(points) rst = local_coords_numpy(o, uvw, points) x = rst[:, 0] y = rst[:, 1] def dist(xc, yc): return sqrt((x - xc) ** 2 + (y - yc) ** 2) def f(c): Ri = dist(*c) return Ri - Ri.mean() xm = mean(x) ym = mean(y) c0 = xm, ym c, ier = leastsq(f, c0) Ri = dist(*c) R = Ri.mean() residu = sum((Ri - R) ** 2) print(residu) xyz = global_coords_numpy(o, uvw, [[c[0], c[1], 0.0]])[0] o = xyz.tolist() u, v, w = uvw.tolist() return o, w, R
def oriented_bounding_box_numpy(points): """Compute the oriented minimum bounding box of a set of points in 3D space. Notes ----- The implementation is based on the convex hull of the points. Parameters ---------- points : list XYZ coordinates of the points. Returns ------- list The XYZ coordinates of the corners of the bounding box. Examples -------- .. plot:: :include-source: from numpy.random import randint from numpy.random import rand import matplotlib.pyplot as plt from compas.plotters import Bounds from compas.plotters import Cloud3D from compas.plotters import Box from compas.plotters import create_axes_3d from compas.geometry import matrix_from_axis_and_angle from compas.geometry import transform_points from compas.geometry import oriented_bounding_box_numpy clouds = [] for i in range(8): a = randint(1, high=8) * 10 * 3.14159 / 180 d = [1, 1, 1] cloud = rand(100, 3) if i in (1, 2, 5, 6): cloud[:, 0] *= - 10.0 cloud[:, 0] -= 3.0 d[0] = -1 else: cloud[:, 0] *= 10.0 cloud[:, 0] += 3.0 if i in (2, 3, 6, 7): cloud[:, 1] *= - 3.0 cloud[:, 1] -= 3.0 d[1] = -1 else: cloud[:, 1] *= 3.0 cloud[:, 1] += 3.0 if i in (4, 5, 6, 7): cloud[:, 2] *= - 6.0 cloud[:, 2] -= 3.0 d[2] = -1 else: cloud[:, 2] *= 6.0 cloud[:, 2] += 3.0 R = matrix_from_axis_and_angle(d, a) cloud[:] = transform_points(cloud, R) clouds.append(cloud.tolist()) axes = create_axes_3d() bounds = Bounds([point for points in clouds for point in points]) bounds.plot(axes) for cloud in clouds: bbox = oriented_bounding_box_numpy(cloud) Cloud3D(cloud).plot(axes) Box(bbox[1]).plot(axes) plt.show() """ points = asarray(points) n, dim = points.shape assert 2 < dim, "The point coordinates should be at least 3D: %i" % dim points = points[:, :3] hull = ConvexHull(points) volume = None bbox = [] # this can be vectorised! for simplex in hull.simplices: abc = points[simplex] uvw = local_axes(abc[0], abc[1], abc[2]) xyz = points[hull.vertices] rst = local_coords_numpy(abc[0], uvw, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt if volume is None or v < volume: rmin, smin, tmin = argmin(rst, axis=0) rmax, smax, tmax = argmax(rst, axis=0) bbox = [ [rst[rmin, 0], rst[smin, 1], rst[tmin, 2]], [rst[rmax, 0], rst[smin, 1], rst[tmin, 2]], [rst[rmax, 0], rst[smax, 1], rst[tmin, 2]], [rst[rmin, 0], rst[smax, 1], rst[tmin, 2]], [rst[rmin, 0], rst[smin, 1], rst[tmax, 2]], [rst[rmax, 0], rst[smin, 1], rst[tmax, 2]], [rst[rmax, 0], rst[smax, 1], rst[tmax, 2]], [rst[rmin, 0], rst[smax, 1], rst[tmax, 2]], ] bbox = global_coords_numpy(abc[0], uvw, bbox) volume = v return hull, bbox, volume
def assembly_interfaces_numpy(assembly, nmax=10, tmax=1e-6, amin=1e-1, lmin=1e-3, face_face=True, face_edge=False, face_vertex=False): """Identify the interfaces between the blocks of an assembly. Parameters ---------- assembly : compas_assembly.datastructures.Assembly An assembly of discrete blocks. nmax : int, optional Maximum number of neighbours per block. Default is ``10``. tmax : float, optional Maximum deviation from the perfectly flat interface plane. Default is ``1e-6``. amin : float, optional Minimum area of a "face-face" interface. Default is ``1e-1``. lmin : float, optional Minimum length of a "face-edge" interface. Default is ``1e-3``. face_face : bool, optional Test for "face-face" interfaces. Default is ``True``. face_edge : bool, optional Test for "face-edge" interfaces. Default is ``False``. face_vertex : bool, optional Test for "face-vertex" interfaces. Default is ``False``. References ---------- The identification of interfaces is discussed in detail here [Frick2016]_. Examples -------- .. code-block:: python pass """ # replace by something proper assembly.edge = {} assembly.halfedge = {} for key in assembly.vertices(): assembly.edge[key] = {} assembly.halfedge[key] = {} key_index = assembly.key_index() index_key = assembly.index_key() blocks = [assembly.blocks[key] for key in assembly.vertices()] nmax = min(nmax, len(blocks)) block_cloud = assembly.get_vertices_attributes('xyz') block_nnbrs = _find_nearest_neighbours(block_cloud, nmax) # k: key of the base block # i: index of the base block # block: base block # nbrs: list of indices of the neighbouring blocks # frames: list of frames for each of the faces of the base block # f0: key of the current base face # A: uvw base frame of f0 # o: origin of the base frame of f0 # xyz0: xyz coordinates of the vertices of f0 # rst0: local coordinates of the vertices of f0, with respect to the frame of f0 # p0: 2D polygon of f0 in local coordinates # j: index of the current neighbour # n: key of the current neighbour # nbr: neighbour block # k_i: key index map for the vertices of the nbr block # xyz: xyz coorindates of all vertices of nbr # rst: local coordinates of all vertices of nbr, with respect to the frame of f0 # f1: key of the current neighbour face # rst1: local coordinates of the vertices of f1, with respect to the frame of f0 # p1: 2D polygon of f1 in local coordinates for k in assembly.vertices(): i = key_index[k] block = assembly.blocks[k] nbrs = block_nnbrs[i][1] frames = block.frames() if face_face: # parallelise? # exclude faces with parallel normals # e.g. exclude overlapping top faces of two neighbouring blocks in same row for f0, (origin, uvw) in frames.items(): A = array(uvw, dtype=float64) o = array(origin, dtype=float64).reshape((-1, 1)) xyz0 = array(block.face_coordinates(f0), dtype=float64).reshape((-1, 3)).T rst0 = solve(A.T, xyz0 - o).T.tolist() p0 = Polygon(rst0) for j in nbrs: n = index_key[j] if n == k: continue if k in assembly.edge and n in assembly.edge[k]: continue if n in assembly.edge and k in assembly.edge[n]: continue nbr = assembly.blocks[n] k_i = {key: index for index, key in enumerate(nbr.vertices())} xyz = array(nbr.get_vertices_attributes('xyz'), dtype=float64).reshape((-1, 3)).T rst = solve(A.T, xyz - o).T.tolist() rst = {key: rst[k_i[key]] for key in nbr.vertices()} for f1 in nbr.faces(): rst1 = [rst[key] for key in nbr.face_vertices(f1)] if any(fabs(t) > tmax for r, s, t in rst1): continue p1 = Polygon(rst1) if p1.area < amin: continue if p0.intersects(p1): intersection = p0.intersection(p1) area = intersection.area if area >= amin: coords = [[x, y, 0.0] for x, y, z in intersection.exterior.coords] coords = global_coords_numpy(o, A, coords) attr = { 'interface_type': 'face_face', 'interface_size': area, 'interface_points': coords.tolist()[:-1], 'interface_origin': origin, 'interface_uvw': uvw, } assembly.add_edge(k, n, attr_dict=attr)
def oriented_bounding_box_numpy(points): """Compute the oriented minimum bounding box of a set of points in 3D space. Parameters ---------- points : array-like XYZ coordinates of the points. Returns ------- array XYZ coordinates of 8 points defining a box. Raises ------ QhullError If the data is essentially 2D. Notes ----- The *oriented (minimum) bounding box* (OBB) of a given set of points is computed using the following procedure: 1. Compute the convex hull of the points. 2. For each of the faces on the hull: 1. Compute face frame. 2. Compute coordinates of other points in face frame. 3. Find "peak-to-peak" (PTP) values of point coordinates along local axes. 4. Compute volume of box formed with PTP values. 3. Select the box with the smallest volume. Examples -------- Generate a random set of points with :math:`x \in [0, 10]`, :math:`y \in [0, 1]` and :math:`z \in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) >>> bottom = numpy.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]) >>> top = numpy.array([[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) >>> points = numpy.concatenate((points, bottom, top)) >>> points[:, 0] *= 10 >>> points[:, 2] *= 3 Rotate the points around an arbitrary axis by an arbitrary angle. >>> from compas.geometry import Rotation >>> from compas.geometry import transform_points_numpy >>> R = Rotation.from_axis_and_angle([1.0, 1.0, 0.0], 0.3 * 3.14159) >>> points = transform_points_numpy(points, R) Compute the volume of the oriented bounding box. >>> bbox = oriented_bounding_box_numpy(points) >>> a = length_vector(subtract_vectors(bbox[1], bbox[0])) >>> b = length_vector(subtract_vectors(bbox[3], bbox[0])) >>> c = length_vector(subtract_vectors(bbox[4], bbox[0])) >>> a * b * c 30.0 """ points = asarray(points) n, dim = points.shape assert 2 < dim, "The point coordinates should be at least 3D: %i" % dim points = points[:, :3] # try: # hull = ConvexHull(points) # except Exception as e: # if 'QH6154' in str(e): # hull = ConvexHull(points, qhull_options='Qb2:0B2:0') # else: # raise e hull = ConvexHull(points) volume = None bbox = [] # this can be vectorised! for simplex in hull.simplices: a, b, c = points[simplex] uvw = local_axes(a, b, c) xyz = points[hull.vertices] rst = local_coords_numpy(a, uvw, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt if volume is None or v < volume: rmin, smin, tmin = amin(rst, axis=0) rmax, smax, tmax = amax(rst, axis=0) bbox = [ [rmin, smin, tmin], [rmax, smin, tmin], [rmax, smax, tmin], [rmin, smax, tmin], [rmin, smin, tmax], [rmax, smin, tmax], [rmax, smax, tmax], [rmin, smax, tmax], ] bbox = global_coords_numpy(a, uvw, bbox) volume = v return bbox