Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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