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 = local_to_world_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 = local_to_world_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd()
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.network.edge = {} assembly.network.halfedge = {} for key in assembly.network.nodes(): assembly.network.edge[key] = {} assembly.network.halfedge[key] = {} key_index = assembly.network.key_index() index_key = assembly.network.index_key() blocks = [assembly.element(key) for key in assembly.network.nodes()] nmax = min(nmax, len(blocks)) block_cloud = assembly.network.nodes_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 nodes of f0 # rst0: local coordinates of the nodes 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 nodes of the nbr block # xyz: xyz coorindates of all nodes of nbr # rst: local coordinates of all nodes of nbr, with respect to the frame of f0 # f1: key of the current neighbour face # rst1: local coordinates of the nodes of f1, with respect to the frame of f0 # p1: 2D polygon of f1 in local coordinates for k in assembly.network.nodes(): i = key_index[k] block = assembly.element(k) nbrs = block_nnbrs[i][1] frames = block.face_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._mesh.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.network.edge and n in assembly.network.edge[ k]: continue if n in assembly.network.edge and k in assembly.network.edge[ n]: continue nbr = assembly.element(n) # print(nbr) k_i = { key: index for index, key in enumerate(nbr._mesh.vertices()) } xyz = array(nbr._mesh.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._mesh.vertices()} for f1 in nbr._mesh.faces(): rst1 = [ rst[key] for key in nbr._mesh.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 = local_to_world_coords_numpy( Frame(o, A[0], A[1]), coords) attr = { 'interface_type': 'face_face', 'interface_size': area, 'interface_points': coords.tolist()[:-1], 'interface_origin': origin, 'interface_uvw': uvw, } assembly.network.add_edge(k, n, attr_dict=attr)
def oriented_bounding_box_numpy(points): r"""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])) >>> close(a * b * c, 30.) True """ 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] frame = [a, uvw[0], uvw[1]] rst = world_to_local_coords_numpy(frame, xyz) rmin, smin, tmin = amin(rst, axis=0) rmax, smax, tmax = amax(rst, axis=0) dr = rmax - rmin ds = smax - smin dt = tmax - tmin v = dr * ds * dt if volume is None or v < volume: 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 = local_to_world_coords_numpy(frame, bbox) volume = v return bbox
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 ----- The point of this function is to find the bestfit frame through the given points and transform the points to make the problem 2D. Once in 2D, the problem simplifies to finding the center point that minimises the difference between the resulting circles for all given points, i.e. minimise in the least squares sense the deviation between the individual radii and the average radius. For more information see [1]_. References ---------- .. [1] Scipy. *Least squares circle*. Available at: http://scipy-cookbook.readthedocs.io/items/Least_Squares_Circle.html. Examples -------- >>> """ o, uvw, _ = pca_numpy(points) frame = [o, uvw[1], uvw[2]] rst = world_to_local_coords_numpy(frame, points) x = rst[:, 0] y = rst[:, 1] def dist(xc, yc): # compute the radius of the circle through each of the given points # for the current centre point return sqrt((x - xc) ** 2 + (y - yc) ** 2) def f(c): # compute the deviation of the radius of each sample point # from the average radius # => minimize this deviation Ri = dist(*c) return Ri - Ri.mean() xm = mean(x) ym = mean(y) c0 = xm, ym c, error = leastsq(f, c0) # compute the radius of the circle through each sample point for the # computed center point. Ri = dist(*c) # compute the radius of the bestfit circle as the average of the individual # radii. R = Ri.mean() # residu = sum((Ri - R) ** 2) # print(residu) # convert the location of the center point back to global coordinates. xyz = local_to_world_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] return xyz, uvw[2], R