Esempio n. 1
0
    def generate_toolpath(self,
                          cutter,
                          models,
                          motion_grid,
                          minz=None,
                          maxz=None,
                          draw_callback=None):
        path = []
        quit_requested = False
        model = pycam.Geometry.Model.get_combined_model(models)

        # Transfer the grid (a generator) into a list of lists and count the
        # items.
        lines = []
        # usually there is only one layer - but an xy-grid consists of two
        for layer in motion_grid:
            for line in layer:
                lines.append(line)

        num_of_lines = len(lines)
        progress_counter = ProgressCounter(len(lines), draw_callback)
        current_line = 0

        args = []
        for one_grid_line in lines:
            # simplify the data (useful for remote processing)
            xy_coords = [(pos[0], pos[1]) for pos in one_grid_line]
            args.append((xy_coords, minz, maxz, model, cutter))
        for points in run_in_parallel(_process_one_grid_line,
                                      args,
                                      callback=progress_counter.update):
            if draw_callback and draw_callback(
                    text="DropCutter: processing line %d/%d" %
                (current_line + 1, num_of_lines)):
                # cancel requested
                quit_requested = True
                break
            for point in points:
                if point is None:
                    # exceeded maxz - the cutter has to skip this point
                    path.append(MoveSafety())
                else:
                    path.append(MoveStraight(point))
                # The progress counter may return True, if cancel was requested.
                if draw_callback and draw_callback(tool_position=point,
                                                   toolpath=path):
                    quit_requested = True
                    break
            # add a move to safety height after each line of moves
            path.append(MoveSafety())
            progress_counter.increment()
            # update progress
            current_line += 1
            if quit_requested:
                break
        return path
Esempio n. 2
0
    def GenerateToolPath(self, cutter, models, motion_grid, minz=None, maxz=None, draw_callback=None):
        quit_requested = False
        model = pycam.Geometry.Model.get_combined_model(models)

        # Transfer the grid (a generator) into a list of lists and count the
        # items.
        lines = []
        # usually there is only one layer - but an xy-grid consists of two
        for layer in motion_grid:
            for line in layer:
                lines.append(line)

        num_of_lines = len(lines)
        progress_counter = ProgressCounter(len(lines), draw_callback)
        current_line = 0

        self.pa.new_direction(0)

        args = []
        for one_grid_line in lines:
            # simplify the data (useful for remote processing)
            xy_coords = [(pos.x, pos.y) for pos in one_grid_line]
            args.append((xy_coords, minz, maxz, model, cutter,
                    self.physics))
        for points in run_in_parallel(_process_one_grid_line, args,
                callback=progress_counter.update):
            self.pa.new_scanline()
            if draw_callback and draw_callback(text="DropCutter: processing " \
                        + "line %d/%d" % (current_line + 1, num_of_lines)):
                # cancel requested
                quit_requested = True
                break
            for point in points:
                if point is None:
                    # exceeded maxz - the cutter has to skip this point
                    self.pa.end_scanline()
                    self.pa.new_scanline()
                    continue
                self.pa.append(point)
                # "draw_callback" returns true, if the user requested to quit
                # via the GUI.
                # The progress counter may return True, if cancel was requested.
                if draw_callback and draw_callback(tool_position=point,
                        toolpath=self.pa.paths):
                    quit_requested = True
                    break
            progress_counter.increment()
            self.pa.end_scanline()
            # update progress
            current_line += 1
            if quit_requested:
                break
        self.pa.end_direction()
        self.pa.finish()
        return self.pa.paths
Esempio n. 3
0
    def GenerateToolPath(self, cutter, models, motion_grid, minz=None, maxz=None, draw_callback=None):
        path = []
        quit_requested = False
        model = pycam.Geometry.Model.get_combined_model(models)

        # Transfer the grid (a generator) into a list of lists and count the
        # items.
        lines = []
        # usually there is only one layer - but an xy-grid consists of two
        for layer in motion_grid:
            for line in layer:
                lines.append(line)

        num_of_lines = len(lines)
        progress_counter = ProgressCounter(len(lines), draw_callback)
        current_line = 0


        args = []
        for one_grid_line in lines:
            # simplify the data (useful for remote processing)
            xy_coords = [(pos[0], pos[1]) for pos in one_grid_line]
            args.append((xy_coords, minz, maxz, model, cutter,
                    self.physics))
        for points in run_in_parallel(_process_one_grid_line, args,
                callback=progress_counter.update):
            if draw_callback and draw_callback(text="DropCutter: processing " \
                        + "line %d/%d" % (current_line + 1, num_of_lines)):
                # cancel requested
                quit_requested = True
                break
            for point in points:
                if point is None:
                    # exceeded maxz - the cutter has to skip this point
                    path.append((MOVE_SAFETY, None))
                else:
                    path.append((MOVE_STRAIGHT, point))
                # The progress counter may return True, if cancel was requested.
                if draw_callback and draw_callback(tool_position=point,
                        toolpath=path):
                    quit_requested = True
                    break
            # add a move to safety height after each line of moves
            path.append((MOVE_SAFETY, None))
            progress_counter.increment()
            # update progress
            current_line += 1
            if quit_requested:
                break
        return path
Esempio n. 4
0
    def GenerateToolPath(self, motion_grid, draw_callback=None):
        # calculate the number of steps

        # Transfer the grid (a generator) into a list of lists and count the
        # items.
        grid = []
        num_of_grid_positions = 0
        for layer in motion_grid:
            lines = []
            for line in layer:
                # convert the generator to a list
                lines.append(list(line))
            num_of_grid_positions += len(lines)
            grid.append(lines)

        num_of_layers = len(grid)

        progress_counter = ProgressCounter(num_of_grid_positions,
                                           draw_callback)

        current_layer = 0
        for layer_grid in grid:
            # update the progress bar and check, if we should cancel the process
            if draw_callback and draw_callback(text="PushCutter: processing" \
                        + " layer %d/%d" % (current_layer + 1, num_of_layers)):
                # cancel immediately
                break

            self.pa.new_direction(0)
            self.GenerateToolPathSlice(layer_grid, draw_callback,
                                       progress_counter)
            self.pa.end_direction()
            self.pa.finish()

            current_layer += 1

        if self._use_polygon_extractor and (len(self.models) > 1):
            other_models = self.models[1:]
            # TODO: this is complicated and hacky :(
            # we don't use parallelism or ODE (for the sake of simplicity)
            final_pa = pycam.PathProcessors.SimpleCutter.SimpleCutter(
                reverse=self.pa.reverse)
            for path in self.pa.paths:
                final_pa.new_scanline()
                pairs = []
                for index in range(len(path.points) - 1):
                    pairs.append((path.points[index], path.points[index + 1]))
                for p1, p2 in pairs:
                    free_points = get_free_paths_triangles(
                        other_models, self.cutter, p1, p2)
                    for point in free_points:
                        final_pa.append(point)
                final_pa.end_scanline()
            final_pa.finish()
            return final_pa.paths
        else:
            return self.pa.paths
Esempio n. 5
0
    def generate_toolpath(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.generate_toolpath_slice(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. 6
0
 def _get_progress_callback(self, update_callback):
     if update_callback:
         return ProgressCounter(self.get_children_count(),
                                update_callback=update_callback).increment
     else:
         return None
Esempio n. 7
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. 8
0
def generate_toolpath(model,
                      tool_settings=None,
                      bounds=None,
                      direction="x",
                      path_generator="DropCutter",
                      path_postprocessor="ZigZagCutter",
                      material_allowance=0,
                      overlap_percent=0,
                      step_down=0,
                      engrave_offset=0,
                      milling_style="ignore",
                      pocketing_type="none",
                      support_model=None,
                      calculation_backend=None,
                      callback=None):
    """ abstract interface for generating a toolpath

    @type model: pycam.Geometry.Model.Model
    @value model: a model contains surface triangles or a contour
    @type tool_settings: dict
    @value tool_settings: contains at least the following keys (depending on
        the tool type):
        "shape": any of possible cutter shape (see "pycam.Cutters")
        "tool_radius": main radius of the tools
        "torus_radius": (only for ToroidalCutter) second toroidal radius
    @type bounds_low: tuple(float) | list(float)
    @value bounds_low: the lower processing boundary (used for the center of
        the tool) (order: minx, miny, minz)
    @type bounds_high: tuple(float) | list(float)
    @value bounds_high: the lower processing boundary (used for the center of
        the tool) (order: maxx, maxy, maxz)
    @type direction: str
    @value direction: any member of the DIRECTIONS set (e.g. "x", "y" or "xy")
    @type path_generator: str
    @value path_generator: any member of the PATH_GENERATORS set
    @type path_postprocessor: str
    @value path_postprocessor: any member of the PATH_POSTPROCESSORS set
    @type material_allowance: float
    @value material_allowance: the minimum distance between the tool and the model
    @type overlap_percent: int
    @value overlap_percent: the overlap between two adjacent tool paths (0..100) given in percent
    @type step_down: float
    @value step_down: maximum height of each layer (for PushCutter)
    @type engrave_offset: float
    @value engrave_offset: toolpath distance to the contour model
    @type calculation_backend: str | None
    @value calculation_backend: any member of the CALCULATION_BACKENDS set
        The default is the triangular collision detection.
    @rtype: pycam.Toolpath.Toolpath | str
    @return: the resulting toolpath object or an error string in case of invalid
        arguments
    """
    log.debug("Starting toolpath generation")
    step_down = number(step_down)
    engrave_offset = number(engrave_offset)
    if bounds is None:
        # no bounds were given - we use the boundaries of the model
        bounds = pycam.Toolpath.Bounds(pycam.Toolpath.Bounds.TYPE_CUSTOM,
                                       (model.minx, model.miny, model.minz),
                                       (model.maxx, model.maxy, model.maxz))
    bounds_low, bounds_high = bounds.get_absolute_limits()
    minx, miny, minz = [number(value) for value in bounds_low]
    maxx, maxy, maxz = [number(value) for value in bounds_high]
    # trimesh model or contour model?
    if isinstance(model, pycam.Geometry.Model.ContourModel):
        # contour model
        trimesh_models = []
        contour_model = model
    else:
        # trimesh model
        trimesh_models = [model]
        contour_model = None
    # Due to some weirdness the height of the drill must be bigger than the
    # object's size. Otherwise some collisions are not detected.
    cutter_height = 4 * abs(maxz - minz)
    cutter = pycam.Cutters.get_tool_from_settings(tool_settings, cutter_height)
    if isinstance(cutter, basestring):
        return cutter
    if not path_generator in ("EngraveCutter", "ContourFollow"):
        # material allowance is not available for these two strategies
        cutter.set_required_distance(material_allowance)
    # create the grid model if requested
    if support_model:
        trimesh_models.append(support_model)
    # Adapt the contour_model to the engraving offset. This offset is
    # considered to be part of the material_allowance.
    if contour_model and (engrave_offset != 0):
        if not callback is None:
            callback(text="Preparing contour model with offset ...")
        contour_model = contour_model.get_offset_model(engrave_offset,
                                                       callback=callback)
        if contour_model:
            return "Failed to calculate offset polygons"
        if not callback is None:
            # reset percentage counter after the contour model calculation
            callback(percent=0)
            if callback(text="Checking contour model with offset for " \
                    + "collisions ..."):
                # quit requested
                return None
            progress_callback = ProgressCounter(
                len(contour_model.get_polygons()), callback).increment
        else:
            progress_callback = None
        result = contour_model.check_for_collisions(callback=progress_callback)
        if result is None:
            return None
        elif result:
            warning = "The contour model contains colliding line groups. " + \
                    "This can cause problems with an engraving offset.\n" + \
                    "A collision was detected at (%.2f, %.2f, %.2f)." % \
                    (result.x, result.y, result.z)
            log.warning(warning)
        else:
            # no collisions and no user interruption
            pass
    # check the pocketing type
    if contour_model and (pocketing_type != "none"):
        if not callback is None:
            callback(text="Generating pocketing polygons ...")
        pocketing_offset = cutter.radius * 1.8
        # TODO: this is an arbitrary limit to avoid infinite loops
        pocketing_limit = 1000
        base_polygons = []
        other_polygons = []
        if pocketing_type == "holes":
            # fill polygons with negative area
            for poly in contour_model.get_polygons():
                if poly.is_closed and not poly.is_outer():
                    base_polygons.append(poly)
                else:
                    other_polygons.append(poly)
        elif pocketing_type == "enclosed":
            # fill polygons with positive area
            pocketing_offset *= -1
            for poly in contour_model.get_polygons():
                if poly.is_closed and poly.is_outer():
                    base_polygons.append(poly)
                else:
                    other_polygons.append(poly)
        else:
            return "Unknown pocketing type given (not one of 'none', " + \
                    "'holes', 'enclosed'): %s" % str(pocketing_type)
        # For now we use only the polygons that do not surround eny other
        # polygons. Sorry - the pocketing is currently very simple ...
        base_filtered_polygons = []
        for candidate in base_polygons:
            if callback and callback():
                return "Interrupted"
            for other in other_polygons:
                if candidate.is_polygon_inside(other):
                    break
            else:
                base_filtered_polygons.append(candidate)
        # start the pocketing for all remaining polygons
        pocket_polygons = []
        for base_polygon in base_filtered_polygons:
            current_queue = [base_polygon]
            next_queue = []
            pocket_depth = 0
            while current_queue and (pocket_depth < pocketing_limit):
                if callback and callback():
                    return "Interrupted"
                for poly in current_queue:
                    result = poly.get_offset_polygons(pocketing_offset)
                    pocket_polygons.extend(result)
                    next_queue.extend(result)
                    pocket_depth += 1
                current_queue = next_queue
                next_queue = []
        # use a copy instead of the original
        contour_model = contour_model.get_copy()
        for pocket in pocket_polygons:
            contour_model.append(pocket)
    # limit the contour model to the bounding box
    if contour_model:
        # use minz/maxz of the contour model (in other words: ignore z)
        contour_model = contour_model.get_cropped_model(
            minx, maxx, miny, maxy, contour_model.minz, contour_model.maxz)
        if contour_model:
            return "No part of the contour model is within the bounding box."
    physics = _get_physics(trimesh_models, cutter, calculation_backend)
    if isinstance(physics, basestring):
        return physics
    generator = _get_pathgenerator_instance(trimesh_models, contour_model,
                                            cutter, path_generator,
                                            path_postprocessor, physics,
                                            milling_style)
    if isinstance(generator, basestring):
        return generator
    overlap = overlap_percent / 100.0
    if (overlap < 0) or (overlap >= 1):
        return "Invalid overlap value (%f): should be greater or equal 0 " \
                + "and lower than 1"
    # factor "2" since we are based on radius instead of diameter
    line_stepping = 2 * number(tool_settings["tool_radius"]) * (1 - overlap)
    if path_generator == "PushCutter":
        step_width = None
    else:
        # the step_width is only used for the DropCutter
        step_width = tool_settings["tool_radius"] / 4
    if path_generator == "DropCutter":
        layer_distance = None
    else:
        layer_distance = step_down
    direction_dict = {
        "x": pycam.Toolpath.MotionGrid.GRID_DIRECTION_X,
        "y": pycam.Toolpath.MotionGrid.GRID_DIRECTION_Y,
        "xy": pycam.Toolpath.MotionGrid.GRID_DIRECTION_XY
    }
    milling_style_grid = {
        "ignore": pycam.Toolpath.MotionGrid.MILLING_STYLE_IGNORE,
        "conventional": pycam.Toolpath.MotionGrid.MILLING_STYLE_CONVENTIONAL,
        "climb": pycam.Toolpath.MotionGrid.MILLING_STYLE_CLIMB
    }
    if path_generator in ("DropCutter", "PushCutter"):
        motion_grid = pycam.Toolpath.MotionGrid.get_fixed_grid(
            (bounds_low, bounds_high),
            layer_distance,
            line_stepping,
            step_width=step_width,
            grid_direction=direction_dict[direction],
            milling_style=milling_style_grid[milling_style])
        if path_generator == "DropCutter":
            toolpath = generator.GenerateToolPath(motion_grid, minz, maxz,
                                                  callback)
        else:
            toolpath = generator.GenerateToolPath(motion_grid, callback)
    elif path_generator == "EngraveCutter":
        if step_down > 0:
            dz = step_down
        else:
            dz = maxz - minz
        toolpath = generator.GenerateToolPath(minz, maxz, step_width, dz,
                                              callback)
    elif path_generator == "ContourFollow":
        if step_down > 0:
            dz = step_down
        else:
            dz = maxz - minz
            if dz <= 0:
                dz = 1
        toolpath = generator.GenerateToolPath(minx, maxx, miny, maxy, minz,
                                              maxz, dz, callback)
    else:
        return "Invalid path generator (%s): not one of %s" \
                % (path_generator, PATH_GENERATORS)
    return toolpath
Esempio n. 9
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. 10
0
    def generate_toolpath(self,
                          cutter,
                          models,
                          motion_grid,
                          minz=None,
                          maxz=None,
                          draw_callback=None):
        # Transfer the grid (a generator) into a list of lists and count the items.
        grid = []
        num_of_grid_positions = 0
        for layer in motion_grid:
            lines = []
            for line in layer:
                # convert the generator to a list
                lines.append(list(line))
            num_of_grid_positions += len(lines)
            grid.append(lines)

        num_of_layers = len(grid)

        progress_counter = ProgressCounter(num_of_grid_positions,
                                           draw_callback)

        current_layer = 0
        if self.waterlines:
            self.pa = pycam.PathProcessors.ContourCutter.ContourCutter()
        else:
            path = []
        for layer_grid in grid:
            # update the progress bar and check, if we should cancel the process
            if draw_callback and draw_callback(
                    text=("PushCutter: processing layer %d/%d" %
                          (current_layer + 1, num_of_layers))):
                # cancel immediately
                break

            if self.waterlines:
                self.pa.new_direction(0)
            result = self.generate_toolpath_slice(cutter, models, layer_grid,
                                                  draw_callback,
                                                  progress_counter)
            if self.waterlines:
                self.pa.end_direction()
                self.pa.finish()
            else:
                path.extend(result)

            current_layer += 1

        if self.waterlines:
            # TODO: this is complicated and hacky :(
            # we don't use parallelism (for the sake of simplicity)
            result = []
            # turn the waterline points into cutting segments
            for path in self.pa.paths:
                pairs = []
                for index in range(len(path.points) - 1):
                    pairs.append((path.points[index], path.points[index + 1]))
                if len(models) > 1:
                    # We assume that the first model is used for the waterline and all
                    # other models are obstacles (e.g. a support grid).
                    other_models = models[1:]
                    for p1, p2 in pairs:
                        free_points = get_free_paths_triangles(
                            other_models, cutter, p1, p2)
                        for index in range(len(free_points) // 2):
                            result.append(MoveStraight(free_points[2 * index]))
                            result.append(
                                MoveStraight(free_points[2 * index + 1]))
                            result.append(MoveSafety())
                else:
                    for p1, p2 in pairs:
                        result.append(MoveStraight(p1))
                        result.append(MoveStraight(p2))
                        result.append(MoveSafety())
            return result
        else:
            return path