コード例 #1
0
ファイル: drawing.py プロジェクト: jf---/compas_viewers
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()
コード例 #2
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.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)
コード例 #3
0
ファイル: bbox_numpy.py プロジェクト: yishizu/compas
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
コード例 #4
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
    -----
    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