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
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
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()
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
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()
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))
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
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()
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
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
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()
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
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
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