def _get_free_paths(self, cutter, models, p1, p2): if self.physics: return get_free_paths_ode(self.physics, p1, p2, depth=self._physics_maxdepth) else: return get_free_paths_triangles(models, cutter, p1, p2)
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
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
def _process_one_line((p1, p2, depth, models, cutter, physics)): #prof = cProfile.Profile() #prof.enable() if physics: points = get_free_paths_ode(physics, p1, p2, depth=depth) else: #points = prof.runcall(get_free_paths_triangles, *(models, cutter, p1, p2)) points = get_free_paths_triangles(models, cutter, p1, p2) #prof.disable() #prof.print_stats(2) return points
def _process_one_line((p1, p2, depth, models, cutter, physics)): #prof = cProfile.Profile() #prof.enable() if physics: points = get_free_paths_ode(physics, p1, p2, depth=depth) else: #points = prof.runcall(get_free_paths_triangles, *(models, cutter, p1, p2)) points = get_free_paths_triangles(models, cutter, p1, p2) #prof.disable() #prof.print_stats(2) return points
def GenerateToolPathLinePush(self, pa, line, z, previous_z, draw_callback=None): if previous_z <= line.minz: # the line is completely above the previous level pass elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1.z) / (line.p2.z - line.p1.z) plane_point = line.p1.add(line.vector.mul(factor)) self.GenerateToolPathLinePush(pa, Line(line.p1, plane_point), z, previous_z, draw_callback=draw_callback) self.GenerateToolPathLinePush(pa, Line(plane_point, line.p2), z, previous_z, draw_callback=draw_callback) elif line.minz < previous_z < line.maxz: plane = Plane(Point(0, 0, previous_z), Vector(0, 0, 1)) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1.z > previous_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp self.GenerateToolPathLinePush(pa, Line(p1, p2), z, previous_z, draw_callback=draw_callback) else: if line.maxz <= z: # the line is completely below z p1 = Point(line.p1.x, line.p1.y, z) p2 = Point(line.p2.x, line.p2.y, z) elif line.minz >= z: p1 = line.p1 p2 = line.p2 else: log.warn("Unexpected condition EC_GTPLP: %s / %s / %s / %s" % \ (line.p1, line.p2, z, previous_z)) return # no model -> no possible obstacles # model is completely below z (e.g. support bridges) -> no obstacles relevant_models = [m for m in self.models if m.maxz >= z] if not relevant_models: points = [p1, p2] elif self.physics: points = get_free_paths_ode(self.physics, p1, p2) else: points = get_free_paths_triangles(relevant_models, self.cutter, p1, p2) if points: for point in points: pa.append(point) if draw_callback: draw_callback(tool_position=points[-1], toolpath=pa.paths)
def GenerateToolPathLinePush(self, pa, line, z, previous_z, draw_callback=None): if previous_z <= line.minz: # the line is completely above the previous level pass elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1.z) / (line.p2.z - line.p1.z) plane_point = line.p1.add(line.vector.mul(factor)) self.GenerateToolPathLinePush(pa, Line(line.p1, plane_point), z, previous_z, draw_callback=draw_callback) self.GenerateToolPathLinePush(pa, Line(plane_point, line.p2), z, previous_z, draw_callback=draw_callback) elif line.minz < previous_z < line.maxz: plane = Plane(Point(0, 0, previous_z), Vector(0, 0, 1)) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1.z > previous_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp self.GenerateToolPathLinePush(pa, Line(p1, p2), z, previous_z, draw_callback=draw_callback) else: if line.maxz <= z: # the line is completely below z p1 = Point(line.p1.x, line.p1.y, z) p2 = Point(line.p2.x, line.p2.y, z) elif line.minz >= z: p1 = line.p1 p2 = line.p2 else: log.warn("Unexpected condition EC_GTPLP: %s / %s / %s / %s" % \ (line.p1, line.p2, z, previous_z)) return # no model -> no possible obstacles # model is completely below z (e.g. support bridges) -> no obstacles relevant_models = [m for m in self.models if m.maxz >= z] if not relevant_models: points = [p1, p2] elif self.physics: points = get_free_paths_ode(self.physics, p1, p2) else: points = get_free_paths_triangles(relevant_models, self.cutter, p1, p2) if points: for point in points: pa.append(point) if draw_callback: draw_callback(tool_position=points[-1], toolpath=pa.paths)
def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): # TODO: there are problems with "material allowance > 0" plane = Plane(Point(0, 0, z), up_vector) if triangle.minz >= z: # no point of the triangle is below z # try all edges # Case (4) proj_points = [] for p in triangle.get_points(): proj_p = plane.get_point_projection(p) if not proj_p in proj_points: proj_points.append(proj_p) if len(proj_points) == 3: edges = [] for index in range(3): edge = Line(proj_points[index - 1], proj_points[index]) # the edge should be clockwise around the model if edge.dir.cross(triangle.normal).dot(up_vector) < 0: edge = Line(edge.p2, edge.p1) edges.append((edge, proj_points[index - 2])) outer_edges = [] for edge, other_point in edges: # pick only edges, where the other point is on the right side if other_point.sub(edge.p1).cross(edge.dir).dot(up_vector) > 0: outer_edges.append(edge) if len(outer_edges) == 0: # the points seem to be an one line # pick the longest edge long_edge = edges[0][0] for edge, other_point in edges[1:]: if edge.len > long_edge.len: long_edge = edge outer_edges = [long_edge] else: edge = Line(proj_points[0], proj_points[1]) if edge.dir.cross(triangle.normal).dot(up_vector) < 0: edge = Line(edge.p2, edge.p1) outer_edges = [edge] else: # some parts of the triangle are above and some below the cutter level # Cases (2a), (2b), (3a) and (3b) points_above = [plane.get_point_projection(p) for p in triangle.get_points() if p.z > z] waterline = plane.intersect_triangle(triangle) if waterline is None: if len(points_above) == 0: # the highest point of the triangle is at z outer_edges = [] else: if abs(triangle.minz - z) < epsilon: # This is just an accuracy issue (see the # "triangle.minz >= z" statement above). outer_edges = [] elif not [p for p in triangle.get_points() if p.z > z + epsilon]: # same as above: fix for inaccurate floating calculations outer_edges = [] else: # this should not happen raise ValueError(("Could not find a waterline, but " \ + "there are points above z level (%f): " \ + "%s / %s") % (z, triangle, points_above)) else: # remove points that are not part of the waterline points_above = [p for p in points_above if (p != waterline.p1) and (p != waterline.p2)] if len(points_above) == 0: # part of case (2a) outer_edges = [waterline] elif len(points_above) == 1: other_point = points_above[0] dot = other_point.sub(waterline.p1).cross(waterline.dir).dot( up_vector) if dot > 0: # Case (2b) outer_edges = [waterline] elif dot < 0: # Case (3b) edges = [] edges.append(Line(waterline.p1, other_point)) edges.append(Line(waterline.p2, other_point)) outer_edges = [] for edge in edges: if edge.dir.cross(triangle.normal).dot(up_vector) < 0: outer_edges.append(Line(edge.p2, edge.p1)) else: outer_edges.append(edge) else: # the three points are on one line # part of case (2a) edges = [] edges.append(waterline) edges.append(Line(waterline.p1, other_point)) edges.append(Line(waterline.p2, other_point)) edges.sort(key=lambda x: x.len) edge = edges[-1] if edge.dir.cross(triangle.normal).dot(up_vector) < 0: outer_edges = [Line(edge.p2, edge.p1)] else: outer_edges = [edge] else: # two points above other_point = points_above[0] dot = other_point.sub(waterline.p1).cross(waterline.dir).dot( up_vector) if dot > 0: # Case (2b) # the other two points are on the right side outer_edges = [waterline] elif dot < 0: # Case (3a) edge = Line(points_above[0], points_above[1]) if edge.dir.cross(triangle.normal).dot(up_vector) < 0: outer_edges = [Line(edge.p2, edge.p1)] else: outer_edges = [edge] else: edges = [] # pick the longest combination of two of these points # part of case (2a) # TODO: maybe we should use the waterline instead? # (otherweise the line could be too long and thus # connections to the adjacent waterlines are not discovered? # Test this with an appropriate test model.) points = [waterline.p1, waterline.p2] + points_above for p1 in points: for p2 in points: if not p1 is p2: edges.append(Line(p1, p2)) edges.sort(key=lambda x: x.len) edge = edges[-1] if edge.dir.cross(triangle.normal).dot(up_vector) < 0: outer_edges = [Line(edge.p2, edge.p1)] else: outer_edges = [edge] # calculate the maximum diagonal length within the model x_dim = abs(model.maxx - model.minx) y_dim = abs(model.maxy - model.miny) z_dim = abs(model.maxz - model.minz) max_length = sqrt(x_dim ** 2 + y_dim ** 2 + z_dim ** 2) result = [] for edge in outer_edges: direction = up_vector.cross(edge.dir).normalized() if direction is None: continue direction = direction.mul(max_length) edge_dir = edge.p2.sub(edge.p1) # TODO: Adapt the number of potential starting positions to the length # of the line. Don't use 0.0 and 1.0 - this could result in ambiguous # collisions with triangles sharing these vertices. for factor in (0.5, epsilon, 1.0 - epsilon, 0.25, 0.75): start = edge.p1.add(edge_dir.mul(factor)) # We need to use the triangle collision algorithm here - because we # need the point of collision in the triangle. collisions = get_free_paths_triangles([model], cutter, start, start.add(direction), return_triangles=True) for index, coll in enumerate(collisions): if (index % 2 == 0) and (not coll[1] is None) \ and (not coll[2] is None) \ and (coll[0].sub(start).dot(direction) > 0): cl, hit_t, cp = coll break else: log.debug("Failed to detect any collision: " \ + "%s / %s -> %s" % (edge, start, direction)) continue proj_cp = plane.get_point_projection(cp) # e.g. the Spherical Cutter often does not collide exactly above # the potential collision line. # TODO: maybe an "is cp inside of the triangle" check would be good? if (triangle is hit_t) or (edge.is_point_inside(proj_cp)): result.append((cl, edge)) # continue with the next outer_edge break # Don't check triangles again that are completely above the z level and # did not return any collisions. if (len(result) == 0) and (triangle.minz > z): # None indicates that the triangle needs no further evaluation return None return result
def _get_free_paths(self, cutter, models, p1, p2): if self.physics: return get_free_paths_ode(self.physics, p1, p2, depth=self._physics_maxdepth) else: return get_free_paths_triangles(models, cutter, p1, p2)
def _get_free_paths(self, cutter, models, p1, p2): return get_free_paths_triangles(models, cutter, p1, p2)
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 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.GenerateToolPathSlice(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 or ODE (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((MOVE_STRAIGHT, free_points[2 * index])) result.append((MOVE_STRAIGHT, free_points[2 * index + 1])) result.append((MOVE_SAFETY, None)) else: for p1, p2 in pairs: result.append((MOVE_STRAIGHT, p1)) result.append((MOVE_STRAIGHT, p2)) result.append((MOVE_SAFETY, None)) return result else: return path
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
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
def _process_one_line(extra_args): p1, p2, models, cutter = extra_args points = get_free_paths_triangles(models, cutter, p1, p2) return points
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