# -*- coding: utf-8 -*- """ $Id$ Copyright 2010 Lars Kruse <devel@sumpfralle.de> Copyright 2008-2009 Lode Leroy This file is part of PyCAM. PyCAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PyCAM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with PyCAM. If not, see <http://www.gnu.org/licenses/>. """ from pycam.PathGenerators import get_free_paths_ode, get_free_paths_triangles import pycam.PathProcessors from pycam.Geometry.utils import ceil from pycam.Utils.threading import run_in_parallel from pycam.Utils import ProgressCounter import pycam.Utils.log import math log = pycam.Utils.log.get_logger() # We need to use a global function here - otherwise it does not work with # the multiprocessing Pool. def _process_one_line((p1, p2, depth, models, cutter, physics)): if physics: points = get_free_paths_ode(physics, p1, p2, depth=depth) else: points = get_free_paths_triangles(models, cutter, p1, p2) return points class PushCutter(object): def __init__(self, path_processor, physics=None): if physics is None: log.debug("Starting PushCutter (without ODE)") else: log.debug("Starting PushCutter (with ODE)") self.pa = path_processor self.physics = physics # check if we use a PolygonExtractor self._use_polygon_extractor = hasattr(self.pa, "polygon_extractor") def GenerateToolPath(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 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(cutter, models, layer_grid, draw_callback, progress_counter) self.pa.end_direction() self.pa.finish() current_layer += 1 if self._use_polygon_extractor and (len(models) > 1): other_models = 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, 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 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