Esempio n. 1
0
def get_points_of_arc(center, radius, a1, a2, plane=None, cords=32):
    """ return the points for an approximated arc

    @param center: center of the circle
    @type center: pycam.Geometry.Point.Point
    @param radius: radius of the arc
    @type radius: float
    @param a1: angle of the start (in degree)
    @type a1: float
    @param a2: angle of the end (in degree)
    @type a2: float
    @param plane: the plane of the circle (default: xy-plane)
    @type plane: pycam.Geometry.Plane.Plane
    @param cords: number of lines for a full circle
    @type cords: int
    @return: a list of lines approximating the arc
    @rtype: list(pycam.Geometry.Line.Line)
    """
    # TODO: implement 3D arc and respect "plane"
    a1 = math.pi * a1 / 180
    a2 = math.pi * a2 / 180
    angle_diff = a2 - a1
    if angle_diff < 0:
        angle_diff += 2 * math.pi
    if angle_diff == 0:
        return []
    num_of_segments = ceil(angle_diff / (2 * math.pi) * cords)
    angle_segment = angle_diff / num_of_segments
    points = []
    get_angle_point = lambda angle: (center.x + radius * math.cos(angle),
                                     center.y + radius * math.sin(angle))
    points.append(get_angle_point(a1))
    for index in range(num_of_segments):
        points.append(get_angle_point(a1 + angle_segment * (index + 1)))
    return points
Esempio n. 2
0
    def GenerateToolPath(self,
                         cutter,
                         models,
                         minx,
                         maxx,
                         miny,
                         maxy,
                         minz,
                         maxz,
                         dz,
                         draw_callback=None):
        # reset the list of processed triangles
        self._processed_triangles = []
        # calculate the number of steps
        # Sometimes there is a floating point accuracy issue: make sure
        # that only one layer is drawn, if maxz and minz are almost the same.
        if abs(maxz - minz) < epsilon:
            diff_z = 0
        else:
            diff_z = abs(maxz - minz)
        num_of_layers = 1 + ceil(diff_z / dz)
        z_step = diff_z / max(1, (num_of_layers - 1))

        # only the first model is used for the contour-follow algorithm
        # TODO: should we combine all models?
        num_of_triangles = len(models[0].triangles(minx=minx,
                                                   miny=miny,
                                                   maxx=maxx,
                                                   maxy=maxy))
        progress_counter = ProgressCounter(
            2 * num_of_layers * num_of_triangles, draw_callback)

        current_layer = 0

        z_steps = [(maxz - i * z_step) for i in range(num_of_layers)]

        # collision handling function
        for z in z_steps:
            # update the progress bar and check, if we should cancel the process
            if draw_callback:
                if draw_callback(text="ContourFollow: processing layer %d/%d" \
                        % (current_layer + 1, num_of_layers)):
                    # cancel immediately
                    break
            self.pa.new_direction(0)
            self.GenerateToolPathSlice(cutter, models[0], minx, maxx, miny,
                                       maxy, z, draw_callback,
                                       progress_counter, num_of_triangles)
            self.pa.end_direction()
            self.pa.finish()
            current_layer += 1
        return self.pa.paths
Esempio n. 3
0
 def __init__(self, model, cutter) :
     self.model = model
     self.diameter = 2*cutter.radius
     self.height = self.diameter # TODO: enfoncement
     self.nb_layers = ceil( (model.maxz - model.minz) / self.height) +1 # TODO: +1 skypocket
     self.layers = []
     #self.heights = Grid.floatrange(model.minz, model.maxz, self.height, False)
     self.heights = [model.minz+i*self.height for i in range(self.nb_layers)]
     self.minx, self.miny, self.minz = model.minx, model.miny, model.minz
     self.maxx, self.maxy, self.maxz = model.maxx, model.maxy, model.maxz
     self.up_vector = Vector(0, 0, 1)
     self.Box = GridBox(self)
     self.do_pavement()
Esempio n. 4
0
    def GenerateToolPathSlice(self, cutter, models, layer_grid, draw_callback=None,
            progress_counter=None):
        path = []

        # settings for calculation of depth
        accuracy = 20
        max_depth = 20
        min_depth = 4

        # the ContourCutter pathprocessor does not work with combined models
        if self.waterlines:
            models = models[:1]
        else:
            models = models

        args = []
        for line in layer_grid:
            p1, p2 = line
            # calculate the required calculation depth (recursion)
            distance = pdist(p2, p1)
            # TODO: accessing cutter.radius here is slightly ugly
            depth = math.log(accuracy * distance / cutter.radius) / math.log(2)
            depth = min(max(ceil(depth), 4), max_depth)
            args.append((p1, p2, depth, models, cutter, self.physics))

        for points in run_in_parallel(_process_one_line, args,
                callback=progress_counter.update):
            if points:
                if self.waterlines:
                    self.pa.new_scanline()
                    for point in points:
                        self.pa.append(point)
                else:
                    for index in range(len(points) / 2):
                        path.append((MOVE_STRAIGHT, points[2 * index]))
                        path.append((MOVE_STRAIGHT, points[2 * index + 1]))
                        path.append((MOVE_SAFETY, None))
                if self.waterlines:
                    if draw_callback:
                        draw_callback(tool_position=points[-1])
                    self.pa.end_scanline()
                else:
                    if draw_callback:
                        draw_callback(tool_position=points[-1], toolpath=path)
            # update the progress counter
            if progress_counter and progress_counter.increment():
                # quit requested
                break

        if not self.waterlines:
            return path
Esempio n. 5
0
 def __init__(self, model, cutter):
     self.model = model
     self.diameter = 2 * cutter.radius
     self.height = self.diameter  # TODO: enfoncement
     self.nb_layers = ceil(
         (model.maxz - model.minz) / self.height) + 1  # TODO: +1 skypocket
     self.layers = []
     #self.heights = Grid.floatrange(model.minz, model.maxz, self.height, False)
     self.heights = [
         model.minz + i * self.height for i in range(self.nb_layers)
     ]
     self.minx, self.miny, self.minz = model.minx, model.miny, model.minz
     self.maxx, self.maxy, self.maxz = model.maxx, model.maxy, model.maxz
     self.up_vector = Vector(0, 0, 1)
     self.Box = GridBox(self)
     self.do_pavement()
Esempio n. 6
0
 def __init__(self, path_processor, physics=None):
     self.pa = path_processor
     self._up_vector = Vector(0, 0, 1)
     self.physics = physics
     self._processed_triangles = []
     if self.physics:
         accuracy = 20
         max_depth = 16
         # TODO: migrate to new interface
         maxx = max([m.maxx for m in self.models])
         minx = max([m.minx for m in self.models])
         maxy = max([m.maxy for m in self.models])
         miny = max([m.miny for m in self.models])
         model_dim = max(abs(maxx - minx), abs(maxy - miny))
         depth = math.log(accuracy * model_dim / self.cutter.radius) / \
                 math.log(2)
         self._physics_maxdepth = min(max_depth, max(ceil(depth), 4))
Esempio n. 7
0
    def GenerateToolPathSlice(self,
                              layer_grid,
                              draw_callback=None,
                              progress_counter=None):
        """ only dx or (exclusive!) dy may be bigger than zero
        """
        # max_deviation_x = dx/accuracy
        accuracy = 20
        max_depth = 20

        # calculate the required number of steps in each direction
        distance = layer_grid[0][-1].sub(layer_grid[0][0]).norm
        step_width = distance / len(layer_grid[0])
        depth = math.log(accuracy * distance / step_width) / math.log(2)
        depth = max(ceil(depth), 4)
        depth = min(depth, max_depth)

        # the ContourCutter pathprocessor does not work with combined models
        if self._use_polygon_extractor:
            models = self.models[:1]
        else:
            models = self.models

        args = []
        for line in layer_grid:
            p1, p2 = line
            args.append((p1, p2, depth, models, self.cutter, self.physics))

        for points in run_in_parallel(_process_one_line,
                                      args,
                                      callback=progress_counter.update):
            if points:
                self.pa.new_scanline()
                for point in points:
                    self.pa.append(point)
                if draw_callback:
                    draw_callback(tool_position=points[-1],
                                  toolpath=self.pa.paths)
                self.pa.end_scanline()
            # update the progress counter
            if progress_counter and progress_counter.increment():
                # quit requested
                break
Esempio n. 8
0
 def GenerateToolPathLineDrop(self, pa, line, minz, maxz, horiz_step, previous_z, draw_callback=None):
     if line.minz >= previous_z:
         # the line is not below maxz -> nothing to be done
         return
     pa.new_direction(0)
     pa.new_scanline()
     if not self.combined_model:
         # no obstacle -> minimum height
         # TODO: this "max(..)" is not correct for inclined lines
         points = [
             Point(line.p1.x, line.p1.y, max(minz, line.p1.z)),
             Point(line.p2.x, line.p2.y, max(minz, line.p2.z)),
         ]
     else:
         # TODO: this "max(..)" is not correct for inclined lines.
         p1 = Point(line.p1.x, line.p1.y, max(minz, line.p1.z))
         p2 = Point(line.p2.x, line.p2.y, max(minz, line.p2.z))
         distance = line.len
         # we want to have at least five steps each
         num_of_steps = max(5, 1 + ceil(distance / horiz_step))
         # steps may be negative
         x_step = (p2.x - p1.x) / (num_of_steps - 1)
         y_step = (p2.y - p1.y) / (num_of_steps - 1)
         x_steps = [(p1.x + i * x_step) for i in range(num_of_steps)]
         y_steps = [(p1.y + i * y_step) for i in range(num_of_steps)]
         step_coords = zip(x_steps, y_steps)
         # TODO: this "min(..)" is not correct for inclided lines. This
         # should be fixed in "get_max_height".
         points = get_max_height_dynamic(
             self.combined_model, self.cutter, step_coords, min(p1.z, p2.z), maxz, self.physics
         )
     for point in points:
         if point is None:
             # exceeded maxz - the cutter has to skip this point
             pa.end_scanline()
             pa.new_scanline()
             continue
         pa.append(point)
     if draw_callback and points:
         draw_callback(tool_position=points[-1], toolpath=pa.paths)
     pa.end_scanline()
     pa.end_direction()
Esempio n. 9
0
    def GenerateToolPathSlice(self,
                              cutter,
                              models,
                              layer_grid,
                              draw_callback=None,
                              progress_counter=None):
        # settings for calculation of depth
        accuracy = 20
        max_depth = 20
        min_depth = 4

        # the ContourCutter pathprocessor does not work with combined models
        if self._use_polygon_extractor:
            models = models[:1]
        else:
            models = models

        args = []
        for line in layer_grid:
            p1, p2 = line
            # calculate the required calculation depth (recursion)
            distance = p2.sub(p1).norm
            # TODO: accessing cutter.radius here is slightly ugly
            depth = math.log(accuracy * distance / cutter.radius) / math.log(2)
            depth = min(max(ceil(depth), 4), max_depth)
            args.append((p1, p2, depth, models, cutter, self.physics))

        for points in run_in_parallel(_process_one_line,
                                      args,
                                      callback=progress_counter.update):
            if points:
                self.pa.new_scanline()
                for point in points:
                    self.pa.append(point)
                if draw_callback:
                    draw_callback(tool_position=points[-1],
                                  toolpath=self.pa.paths)
                self.pa.end_scanline()
            # update the progress counter
            if progress_counter and progress_counter.increment():
                # quit requested
                break
Esempio n. 10
0
    def GenerateToolPath(self, cutter, models, minx, maxx, miny, maxy, minz,
            maxz, dz, draw_callback=None):
        # reset the list of processed triangles
        self._processed_triangles = []
        # calculate the number of steps
        # Sometimes there is a floating point accuracy issue: make sure
        # that only one layer is drawn, if maxz and minz are almost the same.
        if abs(maxz - minz) < epsilon:
            diff_z = 0
        else:
            diff_z = abs(maxz - minz)
        num_of_layers = 1 + ceil(diff_z / dz)
        z_step = diff_z / max(1, (num_of_layers - 1))

        # only the first model is used for the contour-follow algorithm
        # TODO: should we combine all models?
        num_of_triangles = len(models[0].triangles(minx=minx, miny=miny,
                maxx=maxx, maxy=maxy))
        progress_counter = ProgressCounter(2 * num_of_layers * num_of_triangles,
                draw_callback)

        current_layer = 0

        z_steps = [(maxz - i * z_step) for i in range(num_of_layers)]

        # collision handling function
        for z in z_steps:
            # update the progress bar and check, if we should cancel the process
            if draw_callback:
                if draw_callback(text="ContourFollow: processing layer %d/%d" \
                        % (current_layer + 1, num_of_layers)):
                    # cancel immediately
                    break
            self.pa.new_direction(0)
            self.GenerateToolPathSlice(cutter, models[0], minx, maxx, miny, maxy, z,
                    draw_callback, progress_counter, num_of_triangles)
            self.pa.end_direction()
            self.pa.finish()
            current_layer += 1
        return self.pa.paths
Esempio n. 11
0
 def GenerateToolPathLineDrop(self, pa, line, minz, maxz, horiz_step,
         previous_z, draw_callback=None):
     if line.minz >= previous_z:
         # the line is not below maxz -> nothing to be done
         return
     pa.new_direction(0)
     pa.new_scanline()
     if not self.combined_model:
         # no obstacle -> minimum height
         # TODO: this "max(..)" is not correct for inclined lines
         points = [Point(line.p1.x, line.p1.y, max(minz, line.p1.z)),
                 Point(line.p2.x, line.p2.y, max(minz, line.p2.z))]
     else:
         # TODO: this "max(..)" is not correct for inclined lines.
         p1 = Point(line.p1.x, line.p1.y, max(minz, line.p1.z))
         p2 = Point(line.p2.x, line.p2.y, max(minz, line.p2.z))
         distance = line.len
         # we want to have at least five steps each
         num_of_steps = max(5, 1 + ceil(distance / horiz_step))
         # steps may be negative
         x_step = (p2.x - p1.x) / (num_of_steps - 1)
         y_step = (p2.y - p1.y) / (num_of_steps - 1)
         x_steps = [(p1.x + i * x_step) for i in range(num_of_steps)]
         y_steps = [(p1.y + i * y_step) for i in range(num_of_steps)]
         step_coords = zip(x_steps, y_steps)
         # TODO: this "min(..)" is not correct for inclided lines. This
         # should be fixed in "get_max_height".
         points = get_max_height_dynamic(self.combined_model, self.cutter,
                 step_coords, min(p1.z, p2.z), maxz, self.physics)
     for point in points:
         if point is None:
             # exceeded maxz - the cutter has to skip this point
             pa.end_scanline()
             pa.new_scanline()
             continue
         pa.append(point)
     if draw_callback and points:
         draw_callback(tool_position=points[-1], toolpath=pa.paths)
     pa.end_scanline()
     pa.end_direction()
Esempio n. 12
0
    def GenerateToolPathSlice(self, layer_grid, draw_callback=None,
            progress_counter=None):
        """ only dx or (exclusive!) dy may be bigger than zero
        """
        # max_deviation_x = dx/accuracy
        accuracy = 20
        max_depth = 20

        # calculate the required number of steps in each direction
        distance = layer_grid[0][-1].sub(layer_grid[0][0]).norm
        step_width = distance / len(layer_grid[0])
        depth = math.log(accuracy * distance / step_width) / math.log(2)
        depth = max(ceil(depth), 4)
        depth = min(depth, max_depth)

        # the ContourCutter pathprocessor does not work with combined models
        if self._use_polygon_extractor:
            models = self.models[:1]
        else:
            models = self.models

        args = []
        for line in layer_grid:
            p1, p2 = line
            args.append((p1, p2, depth, models, self.cutter, self.physics))

        for points in run_in_parallel(_process_one_line, args,
                callback=progress_counter.update):
            if points:
                self.pa.new_scanline()
                for point in points:
                    self.pa.append(point)
                if draw_callback:
                    draw_callback(tool_position=points[-1], toolpath=self.pa.paths)
                self.pa.end_scanline()
            # update the progress counter
            if progress_counter and progress_counter.increment():
                # quit requested
                break
Esempio n. 13
0
def get_points_of_arc(center, radius, a1, a2, plane=None, cords=32):
    """ return the points for an approximated arc

    @param center: center of the circle
    @type center: pycam.Geometry.Point.Point
    @param radius: radius of the arc
    @type radius: float
    @param a1: angle of the start (in degree)
    @type a1: float
    @param a2: angle of the end (in degree)
    @type a2: float
    @param plane: the plane of the circle (default: xy-plane)
    @type plane: pycam.Geometry.Plane.Plane
    @param cords: number of lines for a full circle
    @type cords: int
    @return: a list of points approximating the arc
    @rtype: list(pycam.Geometry.Point.Point)
    """
    # TODO: implement 3D arc and respect "plane"
    a1 = math.pi * a1 / 180
    a2 = math.pi * a2 / 180
    angle_diff = a2 - a1
    if angle_diff < 0:
        angle_diff += 2 * math.pi
    if angle_diff >= 2 * math.pi:
        angle_diff -= 2 * math.pi
    if angle_diff == 0:
        return []
    num_of_segments = ceil(angle_diff / (2 * math.pi) * cords)
    angle_segment = angle_diff / num_of_segments
    points = []
    get_angle_point = lambda angle: (
            center.x + radius * math.cos(angle),
            center.y + radius * math.sin(angle))
    points.append(get_angle_point(a1))
    for index in range(num_of_segments):
        points.append(get_angle_point(a1 + angle_segment * (index + 1)))
    return points
Esempio n. 14
0
    def GenerateToolPathSlice(self, cutter, models, layer_grid, draw_callback=None,
            progress_counter=None):
        # settings for calculation of depth
        accuracy = 20
        max_depth = 20
        min_depth = 4

        # the ContourCutter pathprocessor does not work with combined models
        if self._use_polygon_extractor:
            models = models[:1]
        else:
            models = models

        args = []
        for line in layer_grid:
            p1, p2 = line
            # calculate the required calculation depth (recursion)
            distance = p2.sub(p1).norm
            # TODO: accessing cutter.radius here is slightly ugly
            depth = math.log(accuracy * distance / cutter.radius) / math.log(2)
            depth = min(max(ceil(depth), 4), max_depth)
            args.append((p1, p2, depth, models, cutter, self.physics))

        for points in run_in_parallel(_process_one_line, args,
                callback=progress_counter.update):
            if points:
                self.pa.new_scanline()
                for point in points:
                    self.pa.append(point)
                if draw_callback:
                    draw_callback(tool_position=points[-1], toolpath=self.pa.paths)
                self.pa.end_scanline()
            # update the progress counter
            if progress_counter and progress_counter.increment():
                # quit requested
                break
Esempio n. 15
0
    def GenerateToolPath(self, minz, maxz, horiz_step, dz, draw_callback=None):
        quit_requested = False
        # calculate the number of steps
        num_of_layers = 1 + ceil(abs(maxz - minz) / dz)
        if num_of_layers > 1:
            z_step = abs(maxz - minz) / (num_of_layers - 1)
            z_steps = [(maxz - i * z_step) for i in range(num_of_layers)]
            # The top layer is treated as the current surface - thus it does not
            # require engraving.
            z_steps = z_steps[1:]
        else:
            z_steps = [minz]
        num_of_layers = len(z_steps)

        current_layer = 0
        num_of_lines = self.contour_model.get_num_of_lines()
        progress_counter = ProgressCounter(len(z_steps) * num_of_lines, draw_callback)

        if draw_callback:
            draw_callback(text="Engrave: optimizing polygon order")
        # Sort the polygons according to their directions (first inside, then
        # outside. This reduces the problem of break-away pieces.
        inner_polys = []
        outer_polys = []
        for poly in self.contour_model.get_polygons():
            if poly.get_area() <= 0:
                inner_polys.append(poly)
            else:
                outer_polys.append(poly)
        inner_sorter = PolygonSorter(inner_polys, callback=draw_callback)
        outer_sorter = PolygonSorter(outer_polys, callback=draw_callback)
        line_groups = inner_sorter.get_polygons() + outer_sorter.get_polygons()
        if self.clockwise:
            for line_group in line_groups:
                line_group.reverse_direction()

        # push slices for all layers above ground
        if maxz == minz:
            # only one layer - use PushCutter instead of DropCutter
            # put "last_z" clearly above the model plane
            last_z = maxz + 1
            push_steps = z_steps
            drop_steps = []
        else:
            # multiple layers
            last_z = maxz
            push_steps = z_steps[:-1]
            drop_steps = [z_steps[-1]]

        for z in push_steps:
            # update the progress bar and check, if we should cancel the process
            if draw_callback and draw_callback(
                text="Engrave: processing" + " layer %d/%d" % (current_layer + 1, num_of_layers)
            ):
                # cancel immediately
                break
            for line_group in line_groups:
                for line in line_group.get_lines():
                    self.GenerateToolPathLinePush(self.pa_push, line, z, last_z, draw_callback=draw_callback)
                    if progress_counter.increment():
                        # cancel requested
                        quit_requested = True
                        # finish the current path
                        self.pa_push.finish()
                        break
            self.pa_push.finish()
            # break the outer loop if requested
            if quit_requested:
                break
            current_layer += 1
            last_z = z

        if quit_requested:
            return self.pa_push.paths

        for z in drop_steps:
            if draw_callback:
                draw_callback(text="Engrave: processing layer %d/%d" % (current_layer + 1, num_of_layers))
            # process the final layer with a drop cutter
            for line_group in line_groups:
                self.pa_drop.new_direction(0)
                self.pa_drop.new_scanline()
                for line in line_group.get_lines():
                    self.GenerateToolPathLineDrop(
                        self.pa_drop, line, z, maxz, horiz_step, last_z, draw_callback=draw_callback
                    )
                    if progress_counter.increment():
                        # quit requested
                        quit_requested = True
                        break
                self.pa_drop.end_scanline()
                self.pa_drop.end_direction()
                # break the outer loop if requested
                if quit_requested:
                    break
            current_layer += 1
            last_z = z
        self.pa_drop.finish()

        return self.pa_push.paths + self.pa_drop.paths
Esempio n. 16
0
    def GenerateToolPath(self, minz, maxz, horiz_step, dz, draw_callback=None):
        quit_requested = False
        # calculate the number of steps
        num_of_layers = 1 + ceil(abs(maxz - minz) / dz)
        if num_of_layers > 1:
            z_step = abs(maxz - minz) / (num_of_layers - 1)
            z_steps = [(maxz - i * z_step) for i in range(num_of_layers)]
            # The top layer is treated as the current surface - thus it does not
            # require engraving.
            z_steps = z_steps[1:]
        else:
            z_steps = [minz]
        num_of_layers = len(z_steps)

        current_layer = 0
        num_of_lines = self.contour_model.get_num_of_lines()
        progress_counter = ProgressCounter(len(z_steps) * num_of_lines,
                draw_callback)

        if draw_callback:
            draw_callback(text="Engrave: optimizing polygon order")
        # Sort the polygons according to their directions (first inside, then
        # outside. This reduces the problem of break-away pieces.
        inner_polys = []
        outer_polys = []
        for poly in self.contour_model.get_polygons():
            if poly.get_area() <= 0:
                inner_polys.append(poly)
            else:
                outer_polys.append(poly)
        inner_sorter = PolygonSorter(inner_polys, callback=draw_callback)
        outer_sorter = PolygonSorter(outer_polys, callback=draw_callback)
        line_groups = inner_sorter.get_polygons() + outer_sorter.get_polygons()
        if self.clockwise:
            for line_group in line_groups:
                line_group.reverse_direction()

        # push slices for all layers above ground
        if maxz == minz:
            # only one layer - use PushCutter instead of DropCutter
            # put "last_z" clearly above the model plane
            last_z = maxz + 1
            push_steps = z_steps
            drop_steps = []
        else:
            # multiple layers
            last_z = maxz
            push_steps = z_steps[:-1]
            drop_steps = [z_steps[-1]]

        for z in push_steps:
            # update the progress bar and check, if we should cancel the process
            if draw_callback and draw_callback(text="Engrave: processing" \
                        + " layer %d/%d" % (current_layer + 1, num_of_layers)):
                # cancel immediately
                break
            for line_group in line_groups:
                for line in line_group.get_lines():
                    self.GenerateToolPathLinePush(self.pa_push, line, z, last_z,
                            draw_callback=draw_callback)
                    if progress_counter.increment():
                        # cancel requested
                        quit_requested = True
                        # finish the current path
                        self.pa_push.finish()
                        break
            self.pa_push.finish()
            # break the outer loop if requested
            if quit_requested:
                break
            current_layer += 1
            last_z = z

        if quit_requested:
            return self.pa_push.paths

        for z in drop_steps:
            if draw_callback:
                draw_callback(text="Engrave: processing layer %d/%d" \
                        % (current_layer + 1, num_of_layers))
            # process the final layer with a drop cutter
            for line_group in line_groups:
                self.pa_drop.new_direction(0)
                self.pa_drop.new_scanline()
                for line in line_group.get_lines():
                    self.GenerateToolPathLineDrop(self.pa_drop, line, z, maxz,
                            horiz_step, last_z, draw_callback=draw_callback)
                    if progress_counter.increment():
                        # quit requested
                        quit_requested = True
                        break
                self.pa_drop.end_scanline()
                self.pa_drop.end_direction()
                # break the outer loop if requested
                if quit_requested:
                    break
            current_layer += 1
            last_z = z
        self.pa_drop.finish()
        
        return self.pa_push.paths + self.pa_drop.paths