def export_svg(self): ''' Exports an svg file at 90 DPI with per-object colors. ''' xmin = self.cad.xmin*self.cad.mm_per_unit dx = (self.cad.xmax - self.cad.xmin)*self.cad.mm_per_unit ymax = self.cad.ymax*self.cad.mm_per_unit dy = (self.cad.ymax - self.cad.ymin)*self.cad.mm_per_unit stroke = max(dx, dy)/100. Path.write_svg_header(self.filename, dx, dy) i = 0 for expr in self.cad.shapes: # Generate an ASDF if self.event.is_set(): return asdf = self.make_asdf(expr, flat=True) i += 1 self.window.progress = i*33/len(self.cad.shapes) # Find the contours of the ASDF if self.event.is_set(): return contours = self.make_contour(asdf) i += 2 self.window.progress = i*33/len(self.cad.shapes) # Write them out to the SVG file for c in contours: c.write_svg_contour( self.filename, xmin, ymax, stroke=stroke, color=expr.color if expr.color else (0,0,0) ) Path.write_svg_footer(self.filename)
def contour(self, bit_diameter, count=1, overlap=0.5): """ @brief Finds a set of isolines on a distance field image. @param bit_diameter Tool diameter (in mm) @param count Number of offsets @param overlap Overlap between offsets @returns A list of Paths """ if self.depth != 'f' or self.channels != 1: raise ValueError('Invalid image type for contour cut '+ '(requires floating-point, 1-channel image)') max_distance = max(self.array.flatten()) levels = [bit_diameter/2] step = bit_diameter * overlap if count == -1: while levels[-1] < max_distance: levels.append(levels[-1] + step) levels[-1] = max_distance else: for i in range(count-1): levels.append(levels[-1] + step) levels = (ctypes.c_float*len(levels))(*levels) ptr = ctypes.POINTER(ctypes.POINTER(Path_))() path_count = libfab.find_paths( self.width, self.height, self.pixels, 1./self.pixels_per_mm, len(levels), levels, ptr) paths = [Path.from_ptr(ptr[i]) for i in range(path_count)] libfab.free_paths(ptr, path_count) return Path.sort(paths)
def export_svg(self): ''' Exports an svg file at 90 DPI with per-object colors. ''' xmin = self.cad.xmin * self.cad.mm_per_unit dx = (self.cad.xmax - self.cad.xmin) * self.cad.mm_per_unit ymax = self.cad.ymax * self.cad.mm_per_unit dy = (self.cad.ymax - self.cad.ymin) * self.cad.mm_per_unit stroke = max(dx, dy) / 100. Path.write_svg_header(self.filename, dx, dy) i = 0 for expr in self.cad.shapes: # Generate an ASDF if self.event.is_set(): return asdf = self.make_asdf(expr, flat=True) i += 1 self.window.progress = i * 33 / len(self.cad.shapes) # Find the contours of the ASDF if self.event.is_set(): return contours = self.make_contour(asdf) i += 2 self.window.progress = i * 33 / len(self.cad.shapes) # Write them out to the SVG file for c in contours: c.write_svg_contour(self.filename, xmin, ymax, stroke=stroke, color=expr.color if expr.color else (0, 0, 0)) Path.write_svg_footer(self.filename)
def finish_cut(self, bit_diameter, overlap, bit_type): ''' Calculates xy and yz finish cuts on a 16-bit heightmap ''' if self.depth != 16 or self.channels != 1: raise ValueError('Invalid image type for finish cut ' + '(requires 16-bit, 1-channel image)') ptr = ctypes.POINTER(ctypes.POINTER(Path_))() path_count = libfab.finish_cut(self.width, self.height, self.pixels, self.mm_per_pixel, self.mm_per_bit, bit_diameter, overlap, bit_type, ptr) paths = [Path.from_ptr(ptr[i]) for i in range(path_count)] libfab.free_paths(ptr, path_count) return paths
def finish_cut(self, bit_diameter, overlap, bit_type): ''' Calculates xy and yz finish cuts on a 16-bit heightmap ''' if self.depth != 16 or self.channels != 1: raise ValueError('Invalid image type for finish cut '+ '(requires 16-bit, 1-channel image)') ptr = ctypes.POINTER(ctypes.POINTER(Path_))() path_count = libfab.finish_cut( self.width, self.height, self.pixels, self.mm_per_pixel, self.mm_per_bit, bit_diameter, overlap, bit_type, ptr) paths = [Path.from_ptr(ptr[i]) for i in range(path_count)] libfab.free_paths(ptr, path_count) return paths
def run(self, asdf): """ @brief Generates one or more toolpaths @param asdf Input ASDF data structure @returns Dictionary with 'planes' (list of list of paths) and 'axis_names' (list of strings) items """ # Parameters used by all operators values = self.get_values([ 'mode', 'res', 'diameter', 'stepover_r', 'tool', 'step', 'stepover_f', 'cut' ]) if not values: return False # Get more parameters based on mode if values['mode'] == 1: v = self.get_values(['alpha', 'beta']) if not v: return False values.update(v) elif values['mode'] == 2: v = self.get_values(['cuts_per']) if not v: return False values.update(v) ## @var planes # List of lists of paths. Each list of paths represent a set of cuts on a given plane. self.planes = [] ## @var axis_names # List of strings representing axis names. self.axis_names = [] if values['mode'] == 0: target_planes = [(0, 0, '+Z'), (180, 90, '+Y'), (0, 90, '-Y'), (90, 90, '-X'), (270, 90, '+X')] elif values['mode'] == 1: target_planes = [(values['alpha'], values['beta'], 'view')] elif values['mode'] == 2: cuts = values['cuts_per'] alphas = [-90 + 180 * v / float(cuts - 1) for v in range(cuts)] betas = [ 360 * v / float(cuts * 2 - 2) for v in range(2 * cuts - 2) ] target_planes = [] for a in alphas: for b in betas: target_planes.append((a, b, '%g, %g' % (a, b))) for a, b, axis in target_planes: koko.FRAME.status = 'Rendering ASDF on %s axis' % axis # Find endmill pointing vector v = -Vec3f(0, 0, 1).deproject(a, b) # Render the ASDF to a bitmap at the appropriate resolution img = asdf.render_multi(resolution=values['res'], alpha=a, beta=b)[0] # Find transformed ADSF bounds bounds = asdf.bounds(a, b) paths = [] if values['cut'] in [0, 2]: paths += self.rough_cut(img, values['diameter'], values['stepover_r'], values['step'], bounds, axis) if values['cut'] in [1, 2]: paths += self.finish_cut(img, values['diameter'], values['stepover_f'], values['tool'], bounds, axis) # Helper function to decide whether a path is inside the # ASDF's bounding box def inside(pt, asdf=asdf): d = values['diameter'] return (pt[0] >= -2 * d and pt[0] <= asdf.X.upper - asdf.X.lower + 2 * d and pt[1] >= -2 * d and pt[1] <= asdf.Y.upper - asdf.Y.lower + 2 * d and pt[2] <= 2 * d and pt[2] >= -(asdf.Z.upper - asdf.Z.lower)) culled = [] for p in paths: for i in range(len(p.points)): p.points[i, :] = list( Vec3f(p.points[i, :]).deproject(a, b)) p.points[i, :] -= [asdf.xmin, asdf.ymin, asdf.zmax] p.points = np.hstack( (p.points, np.array([list(v)] * p.points.shape[0]))) current_path = [] for pt in p.points: if inside(pt): current_path.append(pt) elif current_path: culled.append(Path(np.vstack(current_path))) current_path = [] if current_path: culled.append(Path(np.vstack(current_path))) self.planes.append(culled) self.axis_names.append(axis) paths = reduce(operator.add, self.planes) koko.GLCANVAS.load_paths(paths, asdf.xmin, asdf.ymin, asdf.zmax) return {'planes': self.planes, 'axis_names': self.axis_names}
def run(self, paths): ''' Convert the path from the previous panel into a g-code file ''' koko.FRAME.status = 'Converting to .g file' values = self.get_values() if not values: return False # Reverse direction for climb cutting if values['type']: paths = Path.sort([p.reverse() for p in paths]) # Check to see if all of the z values are the same. If so, # we can use 2D cutting commands; if not, we'll need # to do full three-axis motion control zmin = paths[0].points[0][2] flat = True for p in paths: if not all(pt[2] == zmin for pt in p.points): flat = False # Create a temporary file to store the .sbp instructions self.file = tempfile.NamedTemporaryFile(suffix=self.extension) self.file.write("%%\n") # tape start self.file.write("G17\n") # XY plane self.file.write("G20\n") # Inch mode self.file.write("G40\n") # Cancel tool diameter compensation self.file.write("G49\n") # Cancel tool length offset self.file.write("G54\n") # Coordinate system 1 self.file.write("G80\n") # Cancel motion self.file.write("G90\n") # Absolute programming self.file.write("G94\n") # Feedrate is per minute scale = 1 / 25.4 # inch units self.file.write("T%dM06\n" % values['tool']) # Tool selection + change self.file.write("F%0.4f\n" % (60 * scale * values['feed'])) # Feed rate self.file.write("S%0.4f\n" % values['spindle']) # spindle speed if values['coolant']: self.file.write("M08\n") # coolant on # Move up before starting spindle self.file.write("G00Z%0.4f\n" % (scale * values['jog'])) self.file.write("M03\n") # spindle on (clockwise) self.file.write("G04 P1\n") # pause one second to spin up spindle xy = lambda x, y: (scale * x, scale * y) xyz = lambda x, y, z: (scale * x, scale * y, scale * z) for p in paths: # Move to the start of this path at the jog height self.file.write("G00X%0.4fY%0.4fZ%0.4f\n" % xyz(p.points[0][0], p.points[0][1], values['jog'])) # Plunge to the desired depth self.file.write( "G01Z%0.4f F%0.4f\n" % (p.points[0][2] * scale, 60 * scale * values['plunge'])) # Restore XY feed rate self.file.write("F%0.4f\n" % (60 * scale * values['feed'])) # Cut each point in the segment for pt in p.points: if flat: self.file.write("X%0.4fY%0.4f\n" % xy(*pt[0:2])) else: self.file.write("X%0.4fY%0.4fZ%0.4f\n" % xyz(*pt)) # Lift the bit up to the jog height at the end of the segment self.file.write("Z%0.4f\n" % (scale * values['jog'])) self.file.write("M05\n") # spindle stop if values['coolant']: self.file.write("M09\n") # coolant off self.file.write("M30\n") # program end and reset self.file.write("%%\n") # tape end self.file.flush() koko.FRAME.status = '' return True
def run(self, paths): """ @brief Convert the path from the previous panel into a shopbot file. @param paths List of Paths """ koko.FRAME.status = 'Converting to .sbp file' values = self.get_values() if not values: return False # Reverse direction for climb cutting if values['type']: paths = Path.sort([p.reverse() for p in paths]) # Check to see if all of the z values are the same. If so, # we can use 2D cutting commands; if not, we'll need # to do full three-axis motion control zmin = paths[0].points[0][2] flat = True for p in paths: if not all(pt[2] == zmin for pt in p.points): flat = False ## @var file # tempfile.NamedTemporaryFile to store OpenSBP commands self.file = tempfile.NamedTemporaryFile(suffix=self.extension) self.file.write("SA\r\n") # plot absolute self.file.write("TR,%s,1,\r\n" % values['spindle']) # spindle speed self.file.write("SO,1,1\r\n") # set output number 1 to on self.file.write("pause,2,\r\n") # pause for spindle to spin up scale = 1 if values['units'] else 1/25.4 # mm vs inch units # Cut and jog speeds self.file.write("MS,%f,%f\r\n" % (values['cut_speed']*scale, values['cut_speed']*scale)) self.file.write("JS,%f,%f\r\n" % (values['jog_speed']*scale, values['jog_speed']*scale)) self.file.write("JZ,%f\r\n" % (values['jog']*scale)) # Move up xy = lambda x,y: (scale*x, scale*y) xyz = lambda x,y,z: (scale*x, scale*y, scale*z) for p in paths: # Move to the start of this path with the pen up self.file.write("J2,%f,%f\r\n" % xy(*p.points[0][0:2])) if flat: self.file.write("MZ,%f\r\n" % (zmin*scale)) else: self.file.write("M3,%f,%f,%f\r\n" % xyz(*p.points[0])) # Cut each point in the segment for pt in p.points: if flat: self.file.write("M2,%f,%f\r\n" % xy(*pt[0:2])) else: self.file.write("M3,%f,%f,%f\r\n" % xyz(*pt)) # Lift then pen up at the end of the segment self.file.write("MZ,%f\r\n" % (values['jog']*scale)) self.file.flush() koko.FRAME.status = '' return True
def run(self, paths): ''' Convert the path from the previous panel into a g-code file ''' koko.FRAME.status = 'Converting to .g file' values = self.get_values() if not values: return False # Reverse direction for climb cutting if values['type']: paths = Path.sort([p.reverse() for p in paths]) # Check to see if all of the z values are the same. If so, # we can use 2D cutting commands; if not, we'll need # to do full three-axis motion control zmin = paths[0].points[0][2] flat = True for p in paths: if not all(pt[2] == zmin for pt in p.points): flat = False # Create a temporary file to store the .sbp instructions self.file = tempfile.NamedTemporaryFile(suffix=self.extension) self.file.write("%%\n") # tape start self.file.write("G17\n") # XY plane self.file.write("G20\n") # Inch mode self.file.write("G40\n") # Cancel tool diameter compensation self.file.write("G49\n") # Cancel tool length offset self.file.write("G54\n") # Coordinate system 1 self.file.write("G80\n") # Cancel motion self.file.write("G90\n") # Absolute programming self.file.write("G94\n") # Feedrate is per minute scale = 1/25.4 # inch units self.file.write("T%dM06\n" % values['tool']) # Tool selection + change self.file.write("F%0.4f\n" % (60*scale*values['feed'])) # Feed rate self.file.write("S%0.4f\n" % values['spindle']) # spindle speed if values['coolant']: self.file.write("M08\n") # coolant on # Move up before starting spindle self.file.write("G00Z%0.4f\n" % (scale*values['jog'])) self.file.write("M03\n") # spindle on (clockwise) self.file.write("G04 P1\n") # pause one second to spin up spindle xy = lambda x,y: (scale*x, scale*y) xyz = lambda x,y,z: (scale*x, scale*y, scale*z) for p in paths: # Move to the start of this path at the jog height self.file.write("G00X%0.4fY%0.4fZ%0.4f\n" % xyz(p.points[0][0], p.points[0][1], values['jog'])) # Plunge to the desired depth self.file.write("G01Z%0.4f F%0.4f\n" % (p.points[0][2]*scale, 60*scale*values['plunge'])) # Restore XY feed rate self.file.write("F%0.4f\n" % (60*scale*values['feed'])) # Cut each point in the segment for pt in p.points: if flat: self.file.write("X%0.4fY%0.4f\n" % xy(*pt[0:2])) else: self.file.write("X%0.4fY%0.4fZ%0.4f\n" % xyz(*pt)) # Lift the bit up to the jog height at the end of the segment self.file.write("Z%0.4f\n" % (scale*values['jog'])) self.file.write("M05\n") # spindle stop if values['coolant']: self.file.write("M09\n") # coolant off self.file.write("M30\n") # program end and reset self.file.write("%%\n") # tape end self.file.flush() koko.FRAME.status = '' return True