def __init__(self):
     points = [
         Point(0.000, 0.000, 0.375),
         Point(0.000, 0.020, 0.775),
         Point(0.450, 0.020, 0.775),
         Point(0.520, 0.020, 0.775)
     ]
     super(Staubli_TX260LKinematics, self).__init__(points)
 def __init__(self):
     points = [
         Point(0.175, 0.000, 0.495),
         Point(0.175, 0.000, 1.590),
         Point(1.446, 0.000, 1.765),
         Point(1.581, 0.000, 1.765)
     ]
     super(ABB_IRB4600_40_255Kinematics, self).__init__(points)
Exemple #3
0
 def get_distance(self, point):
     """
     single point distance function
     """
     if not isinstance(point, Point):
         point = Point(*point)
     p = closest_point_on_segment(point, self.segment)
     return point.distance_to_point(p) - self.radius
Exemple #4
0
 def __init__(self, o, e=0.01):
     self.o = o
     self.e = e
     self.ex = Point(e, 0, 0)
     self.ey = Point(0, e, 0)
     self.ez = Point(0, 0, e)
     self.k0 = Vector(1, -1, -1)
     self.k1 = Vector(-1, -1, 1)
     self.k2 = Vector(-1, 1, -1)
     self.k3 = Vector(1, 1, 1)
Exemple #5
0
def select_closest_pt_from_list(Point, pt_list):
    closest_point = None
    distances = [Point.distance_to_point(pt) for pt in pt_list]
    distances.sort()
    min_dis = distances[0]
    for pt in pt_list:
        distance = Point.distance_to_point(pt)
        if distance == min_dis:
            closest_point = pt
    return closest_point, min_dis
 def to_global_geometry(self, world_frame=Frame.worldXY()):
     geometry = self.to_local_geometry_xy()
     transformed_geometry = []
     T = Transformation.from_frame_to_frame(self.frame, world_frame)
     for part in geometry:
         transformed_part = []
         for point in part:
             p_point = Point(point['x'], point['y'], point['z'])
             transformed_part.append(p_point.transformed(T))
         transformed_geometry.append(transformed_part)
     return transformed_geometry
Exemple #7
0
def reorder_vertical_layers(slicer, align_with):
    """Re-orders the vertical layers in a specific way

    Parameters
    ----------
    slicer: :class:`compas_slicer.slicers.BaseSlicer`
        An instance of one of the compas_slicer.slicers classes.
    align_with: str or :class:`compas.geometry.Point`
        x_axis       = reorders the vertical layers starting from the positive x-axis
        y_axis       = reorders the vertical layers starting from the positive y-axis
        Point(x,y,z) = reorders the vertical layers starting from a given Point
    """

    if align_with == "x_axis":
        align_pt = Point(2**32, 0, 0)
    elif align_with == "y_axis":
        align_pt = Point(0, 2**32, 0)
    elif isinstance(align_with, Point):
        align_pt = align_with
    else:
        raise NameError("Unknown align_with : " + str(align_with))

    logger.info(
        "Re-ordering vertical layers to start with the vertical layer closest to: %s"
        % align_with)

    for layer in slicer.layers:
        assert layer.min_max_z_height[0] is not None and layer.min_max_z_height[1] is not None, \
            "To use the 'reorder_vertical_layers function you need first to calculate the layers' z_bounds. To do " \
            "that use the function 'Layer.calculate_z_bounds()'"

    # group vertical layers based on the min_max_z_height
    grouped_iter = itertools.groupby(slicer.layers,
                                     lambda x: x.min_max_z_height)
    grouped_layer_list = [list(group) for _key, group in grouped_iter]

    reordered_layers = []

    for grouped_layers in grouped_layer_list:
        distances = []
        for vert_layer in grouped_layers:
            # recreate head_centroid_pt as compas.Point
            head_centroid_pt = Point(vert_layer.head_centroid[0],
                                     vert_layer.head_centroid[1],
                                     vert_layer.head_centroid[2])
            # measure distance
            distances.append(distance_point_point(head_centroid_pt, align_pt))

        # sort lists based on closest distance to align pt
        grouped_new = [x for _, x in sorted(zip(distances, grouped_layers))]
        reordered_layers.append(grouped_new)

    # flatten list
    slicer.layers = [item for sublist in reordered_layers for item in sublist]
Exemple #8
0
 def get_gradient(self, point):
     """
     central differences, with tetrahedron technique. 30-40% faster regular `get_gradient_regular`.
     """
     d0 = self.o.get_distance(Point(point.x + self.e, point.y - self.e, point.z - self.e))
     d1 = self.o.get_distance(Point(point.x - self.e, point.y - self.e, point.z + self.e))
     d2 = self.o.get_distance(Point(point.x - self.e, point.y + self.e, point.z - self.e))
     d3 = self.o.get_distance(Point(point.x + self.e, point.y + self.e, point.z + self.e))
     v = Vector(d0 - d1 - d2 + d3, -d0 - d1 + d2 + d3, -d0 + d1 - d2 + d3)
     v.unitize()
     return v
Exemple #9
0
    def get_distance(self, point):
        """
        single point distance function
        """
        if not isinstance(point, Point):
            point = Point(*point)

        point.transform(self.inversetransform)

        dxy = length_vector_xy(point)  # distance_point_point_xy(self.torus.center, point)
        d2 = sqrt((dxy - self.torus.radius_axis)**2 + point.z**2)
        return d2 - self.torus.radius_pipe
Exemple #10
0
 def to_global_geometry(self, world_frame=Frame.worldXY()):
     geometry = self.to_local_geometry_xy()
     transformed_geometry = []
     gusset_local_frame = Frame([0, 0, 0], [1, 0, 0], [0, 1, 0])
     T1 = Transformation.from_frame_to_frame(gusset_local_frame, self.frame)
     T2 = Transformation.from_frame_to_frame(self.frame, world_frame)
     transformed_part = []  # default
     for part in geometry:
         transformed_part = []
         for point in part:
             p_point = Point(point['x'], point['y'], point['z'])
             transformed_part.append(p_point.transformed(T1))
     transformed_geometry.append(transformed_part)
     return transformed_geometry
Exemple #11
0
def convert_data_pts_list_to_compas_pts(pt_data_list, ROUND=False):
    """
    pt_coordinates looks like this;
        pt_coordinates = [x, y, z]
    """
    compasPt_list = []
    for pt_data in pt_data_list:
        if ROUND:
            compasPt = Point(round(pt_data[0], 3), round(pt_data[1], 3),
                             round(pt_data[2], 3))
        else:
            compasPt = Point(pt_data[0], pt_data[1], pt_data[2])
        compasPt_list.append(compasPt)
    return compasPt_list
Exemple #12
0
    def get_distance(self, point):
        """
        single point distance function
        """
        if not isinstance(point, Point):
            point = Point(*point)

        point.transform(self.inversetransform)

        dxy = length_vector_xy(
            point)  # distance_point_point_xy(self.cylinder.center, point)
        d = dxy - self.cylinder.radius
        d = max(d, abs(point.z) - self.cylinder.height / 2.0)
        return d
Exemple #13
0
    def get_distance(self, point):

        if not isinstance(point, Point):
            point = Point(*point)
        point.transform(self.inversetransform)
        x, y, z = point
        
        # Tetrahedron
        if self.type == 0:
            return (max(abs(x + y) - z, abs(x - y) + z) - self.radius) / self.sqrt3
        
        # Octahedron
        elif self.type == 1:
            s = abs(x) + abs(y) + abs(z)
            return (s - self.radius) * self.tan30
        
        # Dodecahedron
        elif self.type == 2:
            v = Vector((1+sqrt(5))/2, 1, 0)
            v.unitize()
            px = abs(x / self.radius)
            py = abs(y / self.radius)
            pz = abs(z / self.radius)
            p = Vector(px, py, pz)
            a = p.dot(v)
            b = p.dot(Vector(v.z, v.x, v.y))
            c = p.dot(Vector(v.y, v.z, v.x))
            q = (max(max(a, b), c) - v.x) * self.radius
            return q
        
        # Icosahedron
        elif self.type == 3:
            r = self.radius * 0.8506507174597755
            v = Vector((sqrt(5) + 3)/2, 1, 0)
            v.unitize()
            w = sqrt(3)/3
            px = abs(x / r)
            py = abs(y / r)
            pz = abs(z / r)

            p = Vector(px, py, pz)
            a = p.dot(v)
            b = p.dot(Vector(v.z, v.x, v.y))
            c = p.dot(Vector(v.y, v.z, v.x))
            d = p.dot([w,w,w]) - v.x
            q = max(max(max(a, b), c) - v.x, d) * r
            return q
        
        else:
            return 0
Exemple #14
0
    def face_subdiv_frame(self, fkey, rel_pos=None, move_z=None, **kwattr):
        if rel_pos == 1 or rel_pos == [1, 1, 1]:
            return self.face_subdiv_pyramid(fkey, move_z=move_z, **kwattr)

        face_center_pt = Point(*self.face_center(fkey))
        face_normal = Vector(*self.face_normal(fkey))
        face_coordinates = self.face_coordinates(fkey)
        face_halfedges = self.face_halfedges(fkey)

        self.delete_face(fkey)

        if move_z is None:
            move_z = cycle(to_list(0))

        if not isinstance(move_z, cycle):
            move_z = cycle(to_list(move_z))

        if rel_pos is None:
            rel_pos = cycle(to_list(0.5))

        if not isinstance(rel_pos, cycle):
            rel_pos = cycle(to_list(rel_pos))

        new_vkeys = []
        for x, y, z in face_coordinates:
            pt = Point(x, y, z)
            factor = next(rel_pos)

            v = face_center_pt - pt
            pt += v * factor
            if move_z:
                z_factor = next(move_z)
                pt += face_normal * z_factor
            new_vkeys.append(self.add_vertex(x=pt.x, y=pt.y, z=pt.z))

        new_fkeys = []
        for i, uv in enumerate(face_halfedges):
            u, v = uv
            vkeys = []
            vkeys.append(u)
            vkeys.append(v)
            vkeys.append(new_vkeys[(i + 1) % len(new_vkeys)])
            vkeys.append(new_vkeys[i])

            new_fkeys.append(self.add_face(vkeys))

        # add new center face
        new_fkeys.append(self.add_face(new_vkeys))

        return new_vkeys, new_fkeys
Exemple #15
0
    def __init__(self, scene, arrow, **kwargs):
        super(ArrowObject, self).__init__(scene, arrow, **kwargs)

        length = np.linalg.norm(arrow.direction)

        plane = Plane(arrow.point + arrow.direction * 0.7, arrow.direction)
        circle = Circle(plane, length * 0.15)
        cone = Cone(circle, length * 0.3)

        line = Line(Point(*arrow.point),
                    Point(*(arrow.point + arrow.direction * 0.7)))

        self.view = ArrowView(arrow, ConeObject(None, cone, 3),
                              LineObject(None, line))
def create_overhang_texture(slicer, overhang_distance):
    """Creates a cool overhang texture"""

    print("Creating cool texture")

    for i, layer in enumerate(slicer.layers):
        if i % 10 == 0 and i > 0:
            # for every 10th layer, except for the brim
            # print(layer)
            for j, path in enumerate(layer.paths):
                # print(path)
                # create an empty layer in which we can store our modified points
                new_path = []
                for k, pt in enumerate(path.points):
                    # for every second point (only even points)
                    if k % 2 == 0:
                        # get the normal of the point in relation to the mesh
                        normal = get_normal_of_path_on_xy_plane(k,
                                                                pt,
                                                                path,
                                                                mesh=None)
                        # scale the vector by a number to move the point
                        normal_scaled = scale_vector(normal,
                                                     -overhang_distance)
                        # create a new point by adding the point and the normal vector
                        new_pt = add_vectors(pt, normal_scaled)
                        # recreate the new_pt values as compas_points
                        pt = Point(new_pt[0], new_pt[1], new_pt[2])

                    # append the points to the new path
                    new_path.append(pt)

                # replace the current path with the new path that we just created
                layer.paths[j] = Path(new_path, is_closed=path.is_closed)
Exemple #17
0
def test_booleans():

    # ==============================================================================
    # Make a box and a sphere
    # ==============================================================================

    box = Box.from_width_height_depth(2, 2, 2)
    box = Mesh.from_shape(box)
    box.quads_to_triangles()

    A = box.to_vertices_and_faces()

    sphere = Sphere(Point(1, 1, 1), 1)
    sphere = Mesh.from_shape(sphere, u=30, v=30)
    sphere.quads_to_triangles()

    B = sphere.to_vertices_and_faces()

    # ==============================================================================
    # Remesh the sphere
    # ==============================================================================

    B = remesh(B, 0.3, 10)

    # ==============================================================================
    # Compute the boolean mesh
    # ==============================================================================

    V, F = boolean_union(A, B)

    mesh = Mesh.from_vertices_and_faces(V, F)
Exemple #18
0
    def generate_paths(self):
        """Generates the planar slicing paths."""
        z = [
            self.mesh.vertex_attribute(key, 'z')
            for key in self.mesh.vertices()
        ]
        min_z, max_z = min(z), max(z)
        d = abs(min_z - max_z)
        no_of_layers = int(d / self.layer_height) + 1
        normal = Vector(0, 0, 1)
        planes = [
            Plane(Point(0, 0, min_z + i * self.layer_height), normal)
            for i in range(no_of_layers)
        ]
        # planes.pop(0)  # remove planes that are on the print platform

        if self.slicer_type == "default":
            logger.info('')
            logger.info("Planar slicing using default function ...")
            self.layers = compas_slicer.slicers.create_planar_paths(
                self.mesh, planes)

        elif self.slicer_type == "cgal":
            logger.info('')
            logger.info("Planar slicing using CGAL ...")
            self.layers = compas_slicer.slicers.create_planar_paths_cgal(
                self.mesh, planes)

        else:
            raise NameError("Invalid slicing type : " + self.slicer_type)
Exemple #19
0
    def from_data(cls, data):
        """Construct a BSpline surface from its data representation.

        Parameters
        ----------
        data : dict
            The data dictionary.

        Returns
        -------
        :class:`compas.geometry.NurbsSurface`
            The constructed surface.

        """
        points = [[Point.from_data(point) for point in row]
                  for row in data['points']]
        weights = data['weights']
        u_knots = data['u_knots']
        v_knots = data['v_knots']
        u_mults = data['u_mults']
        v_mults = data['v_mults']
        u_degree = data['u_degree']
        v_degree = data['v_degree']
        is_u_periodic = data['is_u_periodic']
        is_v_periodic = data['is_v_periodic']
        return cls.from_parameters(points, weights, u_knots, v_knots, u_mults,
                                   v_mults, u_degree, v_degree, is_u_periodic,
                                   is_v_periodic)
Exemple #20
0
def main():
    compas_mesh = Mesh.from_obj(os.path.join(DATA, MODEL))
    move_mesh_to_point(compas_mesh, Point(0, 0, 0))

    # Slicing
    slicer = PlanarSlicer(compas_mesh, slicer_type="cgal", layer_height=5.0)
    slicer.slice_model()

    # Sorting into vertical layers and reordering
    sort_into_vertical_layers(slicer, max_paths_per_layer=10)
    reorder_vertical_layers(slicer, align_with="x_axis")

    # Post-processing
    generate_brim(slicer, layer_width=3.0, number_of_brim_offsets=5)
    simplify_paths_rdp_igl(slicer, threshold=0.7)
    seams_smooth(slicer, smooth_distance=10)
    slicer.printout_info()
    save_to_json(slicer.to_data(), OUTPUT_DIR, 'slicer_data.json')

    # PlanarPrintOrganization
    print_organizer = PlanarPrintOrganizer(slicer)
    print_organizer.create_printpoints()

    set_extruder_toggle(print_organizer, slicer)
    add_safety_printpoints(print_organizer, z_hop=10.0)
    set_linear_velocity_constant(print_organizer, v=25.0)
    set_blend_radius(print_organizer, d_fillet=10.0)

    print_organizer.printout_info()

    printpoints_data = print_organizer.output_printpoints_dict()
    utils.save_to_json(printpoints_data, OUTPUT_DIR, 'out_printpoints.json')
Exemple #21
0
    def face_subdiv_mid2center(self, fkey, rel_pos=None, move_z=None, **kwattr):
        xyz = Point(*self.face_center(fkey))

        x, y, z = self._move_center_pt(xyz, fkey, rel_pos, move_z)

        face_halfedges = self.face_halfedges(fkey)

        new_vkeys, new_fkeys, deleted_faces = self._split_edges(fkey, face_halfedges)

        deleted_faces.append((fkey, self.face_vertices(fkey)))
        self.delete_face(fkey)

        center_vkey = self.add_vertex(x=x, y=y, z=z)

        for i, face_halfedge in enumerate(face_halfedges):
            face_vertex, _ = face_halfedge
            edge_mid = new_vkeys[i]
            prev_edge_mid = new_vkeys[(i - 1) % len(new_vkeys)]

            vkeys = [face_vertex, edge_mid, center_vkey, prev_edge_mid]
            new_fkeys.append(self.add_face(vkeys))

        new_vkeys.append(center_vkey)

        return new_vkeys, new_fkeys, deleted_faces
Exemple #22
0
    def draw(self, point=None, show_point=False):
        """Draw the vector.

        Parameters
        ----------
        point : [float, float, float] or :class:`compas.geometry.Point`, optional
            Point of application of the vector.
            Default is ``Point(0, 0, 0)``.
        show_point : bool, optional
            If True, draw the base point of the vector.

        Returns
        -------
        list[System.Guid]
            The GUIDs of the created Rhino objects.

        """
        if not point:
            point = [0, 0, 0]
        start = Point(*point)
        end = start + self.primitive
        start = list(start)
        end = list(end)
        guids = []
        if show_point:
            points = [{'pos': start, 'color': self.color, 'name': self.primitive.name}]
            guids += compas_rhino.draw_points(points, layer=self.layer, clear=False, redraw=False)
        lines = [{'start': start, 'end': end, 'arrow': 'end', 'color': self.color, 'name': self.primitive.name}]
        guids += compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False)
        return guids
Exemple #23
0
    def face_subdiv_mid_cent(self, fkey, rel_pos=None, move_z=None, **kwattr):
        xyz = Point(*self.face_center(fkey))

        x, y, z = self._move_center_pt(xyz, fkey, rel_pos, move_z)

        face_vertices = self.face_vertices(fkey)
        face_halfedges = self.face_halfedges(fkey)

        self.delete_face(fkey)

        center_vkey = self.add_vertex(x=x, y=y, z=z)

        new_vkeys = []
        for u, v in face_halfedges:
            x, y, z = self.edge_midpoint(u, v)
            new_vkeys.append(self.add_vertex(x=x, y=y, z=z))

        new_fkeys = []
        for i, face_vertex in enumerate(face_vertices):
            edge_mid = new_vkeys[i]
            prev_edge_mid = new_vkeys[(i - 1) % len(new_vkeys)]
            vkeys = [face_vertex, edge_mid, center_vkey, prev_edge_mid]
            new_fkeys.append(self.add_face(vkeys))

        new_vkeys.append(center_vkey)

        return new_vkeys, new_fkeys
Exemple #24
0
    def create_base_boundaries(self):
        """ Creates one BaseBoundary per vertical_layer."""
        bs = []
        root_vs = utils.get_mesh_vertex_coords_with_attribute(
            self.slicer.mesh, 'boundary', 1)
        root_boundary = BaseBoundary(self.slicer.mesh,
                                     [Point(*v) for v in root_vs])

        if len(self.vertical_layers) > 1:
            for i, vertical_layer in enumerate(self.vertical_layers):
                parents_of_current_node = self.topo_sort_graph.get_parents_of_node(
                    i)
                if len(parents_of_current_node) == 0:
                    boundary = root_boundary
                else:
                    boundary_pts = []
                    for parent_index in parents_of_current_node:
                        parent = self.vertical_layers[parent_index]
                        boundary_pts.extend(parent.paths[-1].points)
                    boundary = BaseBoundary(self.slicer.mesh, boundary_pts)
                bs.append(boundary)
        else:
            bs.append(root_boundary)

        # save intermediary outputs
        b_data = {i: b.to_data() for i, b in enumerate(bs)}
        utils.save_to_json(b_data, self.OUTPUT_PATH, 'boundaries.json')

        return bs
Exemple #25
0
def get_mid_pt_base(mesh):
    """Gets the middle point of the base (bottom) of the mesh.

    Parameters
    ----------
    mesh: :class:`compas.datastructures.Mesh`
        A compas mesh.

    Returns
    -------
    mesh_mid_pt: :class:`compas.geometry.Point`
        Middle point of the base of the mesh.

    """
    # get center bottom point of mesh model
    bbox = mesh_bounding_box(mesh)
    corner_pts = [bbox[0], bbox[2]]

    x = [p[0] for p in corner_pts]
    y = [p[1] for p in corner_pts]
    z = [p[2] for p in corner_pts]

    mesh_mid_pt = Point((sum(x) / 2), (sum(y) / 2), (sum(z) / 2))

    return mesh_mid_pt
Exemple #26
0
def pick_frame_from_grid(index, bullet_height):
    """Get next picking frame.

    Parameters
    ----------
    index : int
        Counter to iterate through picking positions.
    bullet_height : float
        Height of bullet to pick up.

    Returns
    -------
    list of `class`:compas.geometry.Frame
    """
    # If index is larger than amount on picking plate, start from zero again
    index = index % (fab_conf["pick"]["xnum"].get() *
                     fab_conf["pick"]["ynum"].get())

    xpos = index % fab_conf["pick"]["xnum"].get()
    ypos = index // fab_conf["pick"]["xnum"].get()

    x = (fab_conf["pick"]["origin_grid"]["x"].get() +
         xpos * fab_conf["pick"]["grid_spacing"].get())
    y = (fab_conf["pick"]["origin_grid"]["y"].get() +
         ypos * fab_conf["pick"]["grid_spacing"].get())
    z = bullet_height * fab_conf["pick"]["compression_height_factor"].get()

    frame = Frame(
        Point(x, y, z),
        Vector(*fab_conf["pick"]["xaxis"].get()),
        Vector(*fab_conf["pick"]["yaxis"].get()),
    )
    log.debug("Picking frame {:03d}: {}".format(index, frame))
    return frame
def simplify_paths_rdp(slicer, threshold):
    """Simplifies a path using the Ramer–Douglas–Peucker algorithm, implemented in the rdp python library.
    https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm

    Parameters
    ----------
    slicer: :class:`compas_slicer.slicers.BaseSlicer`
        An instance of one of the compas_slicer.slicers classes.
    threshold: float
        Controls the degree of polyline simplification.
        Low threshold removes few points, high threshold removes many points.
    """

    logger.info("Paths simplification rdp")
    remaining_pts_num = 0

    with progressbar.ProgressBar(max_value=len(slicer.layers)) as bar:
        for i, layer in enumerate(slicer.layers):
            if not layer.is_raft:  # no simplification necessary for raft layer
                for path in layer.paths:
                    pts_rdp = rdp.rdp(np.array(path.points), epsilon=threshold)
                    path.points = [
                        Point(pt[0], pt[1], pt[2]) for pt in pts_rdp
                    ]
                    remaining_pts_num += len(path.points)
                    bar.update(i)
        logger.info('%d Points remaining after rdp simplification' %
                    remaining_pts_num)
Exemple #28
0
def quad_to_face(points: Quad) -> TopoDS_Face:
    """Convert a quad to a BRep face with an underlying ruled surface.

    Parameters
    ----------
    points : [point, point, point, point]
        Four points defining a quad.

    Returns
    -------
    TopoDS_Face

    Raises
    ------
    AssertionError
        If the number of points is not 4.

    """
    assert len(points) == 4, "The number of input points should be four."

    points = [Point(*point) for point in points]
    curve1 = GeomAPI_PointsToBSpline(
        array1_from_points1([points[0], points[1]])).Curve()
    curve2 = GeomAPI_PointsToBSpline(
        array1_from_points1([points[3], points[2]])).Curve()
    srf = geomfill_Surface(curve1, curve2)
    return BRepBuilderAPI_MakeFace(srf, 1e-6).Face()
def simplify_paths_rdp_igl(slicer, threshold):
    """
    https://libigl.github.io/libigl-python-bindings/igl_docs/#ramer_douglas_peucker
    Parameters
    ----------
    slicer: :class:`compas_slicer.slicers.BaseSlicer`
        An instance of one of the compas_slicer.slicers classes.
    threshold: float
        Controls the degree of polyline simplification.
        Low threshold removes few points, high threshold removes many points.
    """
    try:
        utils.check_package_is_installed('igl')
        logger.info("Paths simplification rdp - igl")
        remaining_pts_num = 0

        for i, layer in enumerate(slicer.layers):
            if not layer.is_raft:  # no simplification necessary for raft layer
                for path in layer.paths:
                    pts = np.array([[pt[0], pt[1], pt[2]]
                                    for pt in path.points])
                    S, J, Q = igl.ramer_douglas_peucker(pts, threshold)
                    path.points = [Point(pt[0], pt[1], pt[2]) for pt in S]
        logger.info('%d Points remaining after rdp simplification' %
                    remaining_pts_num)

    except PluginNotInstalledError:
        logger.info(
            "Libigl is not installed. Falling back to python rdp function")
        simplify_paths_rdp(slicer, threshold)
def test_sphere_scaling():
    s = Sphere(Point(1, 1, 1), 10)
    s.transform(Scale.from_factors([2, 3, 4]))
    assert s.point.x == 2
    assert s.point.y == 3
    assert s.point.z == 4
    assert s.radius == 40