Fri, 03 Jun 2016 21:14:09 +0200
Bugfixing, Added M400 magic
""" Lasercutter library 2015/2016 by NeoSoft, Malte Bayer Intended to use standalone or implemented in Pronterface/Printrun """ """ LASERCUT SETTINGS TODO: move to printrun settings """ ENGRAVE_SPEED = 10 * 60 # mm/min # 30mm/min works for wood (regulate the output power to something between 10-30%) # 30mm/min for black anodized aluminum to get a light engraving @ 100% power # 10mm/min for black anodized aluminum to get more "silver" @ 100% power TRAVEL_SPEED = 120 * 60 E_FACTOR = 0.5 # BITMAP: DPI = 300 GREY_THRESHOLD = 0 CHANGE_DIRECTION = True INVERT_CUT = True """ STATIC DEFINITIONS DO NOT CHANGE WORLD's RULES! """ INCH = 25.4 # mm MM_PIXEL = round(INCH / DPI, 4) STEPS_PIXEL = MM_PIXEL * 80 # mine is 80 steps/mm on XY # FOR HPGL: SCALE_FACTOR = 1.0 / 40.0 # 40 plotter units from PIL import Image import sys # Imports for SVG import xml.etree.ElementTree as ET import math from svg2gcode import shapes as shapes_pkg from svg2gcode.shapes import point_generator class Lasercutter: """ Lasercutter methods parameters: log = logger function (accepts a string) """ def __init__(self, pronterwindow = None): if pronterwindow: self.pronterwindow = pronterwindow self.log = pronterwindow.log else: self.pronterwindow = None self.log = lambda : None self.log("\nLasercutter library initialized resolution: %f mm per pixel" % MM_PIXEL) if STEPS_PIXEL <= 5: self.log("WARNING: STEPS PER PIXEL NEEDS TO BE > 5 (otherwise marlin joins lines): %f" % STEPS_PIXEL) def pixel2bit(self, pixel, threshold=128): """Convert the pixel value to a bit.""" # some really weird stuff here ;-P # RGB to greyscale #print pixel #print type(pixel) if isinstance(pixel, tuple): #rgb pixel = pixel[0]*0.2989 + pixel[1]*0.5870 + pixel[2]*0.1140 threshold = 128 if pixel > threshold: return 1 else: return 0 # color palette if pixel <= threshold: return 1 else: return 0 def image2gcode(self, filename): """ Open a image file and get the basic information about it. Then convert it to gcode (replacing the existing gcode buffer contents) """ try: im = Image.open(filename) except: self.log("Unable to open %s" % filename) return False self.log("Converting Image for lasercut:") self.log("File: %s" % filename) self.log("format: %s, mode: %s" % (im.format, im.mode)) width,height = im.size self.log("size: %d x %d pixels" % im.size) pix = im.load() fo = open(filename + ".g", "w") fo.write(""" ; Filename: %s ; GCode generated by bitplotter one-night-quick-hack script (marlin code flavour) ; 2015/2016 by NeoSoft - Malte Bayer G21 ; Metric ; We assume Z is in focus height and laser head is focus at bottom left of image! G92 X0 Y0 E0; set zero position - new origin G90 ; absolute positioning M82 ; Set extruder (laser) to absolute positioning M201 X1000 Y1000 E500 ; Set acceleration M203 X1000 Y1000 Z4 E10 ; Set max feedrate M209 S0 ; disable firmware retraction, we dont want to burn holes... M302 ; Allow cold extrudes - doesnt matter because we hack the extruder physically off with the M571 E mod M571 S1 E1 ; Activate Laser output on extrusion, but block real motor movement! G0 X0 Y0 F%d ; Set moving speed TRAVEL_SPEED G1 X0 Y0 F%d ; Set linear engraving speed ENGRAVE_SPEED """ % (filename, TRAVEL_SPEED, ENGRAVE_SPEED) ) self.log("Travel/Engrave speed: %d mm/sec, %d mm/sec" % ( TRAVEL_SPEED / 60, ENGRAVE_SPEED / 60) ) fo.write(";Start engraving the raster image: %dx%d points @ %d DPI = %.0fx%.0f mm\n\n" % ( im.size[0], im.size[1], DPI, im.size[0]*MM_PIXEL, im.size[1]*MM_PIXEL) ) INVERT_Y = MM_PIXEL * (im.size[1] -1) * (-1) DIR = 1 for X in range(im.size[0]): fo.write("; X=%d printing row: direction %i\n" % (X, DIR)) fo.write("G92 E0\n") E = 0 last_bit = 1 # we engrave on black pixel = 0 START_Y = 0 if DIR > 0: range_start = 0 range_stop = im.size[1] else: range_start = im.size[1] -1 range_stop = -1 for Y in range(range_start, range_stop, DIR): YMM = abs((Y * MM_PIXEL) + INVERT_Y) XMM = X * MM_PIXEL #print "X %d Y %d" % (X, Y) bit = self.pixel2bit(pix[X, Y], GREY_THRESHOLD) if INVERT_CUT: if bit == 0: bit = 1 else: bit = 0 if last_bit == bit: if bit == 1: # nothing to do, continue else: # are we at the end of Y range? #print Y if (Y == (im.size[1] - 1)) or (Y == 0): # draw line if DIR > 0: E = E + MM_PIXEL * (Y - START_Y) else: E = E + MM_PIXEL * (START_Y - Y) fo.write("G1 X%.4f Y%.4f E%.4f\n" % (XMM, YMM, E * E_FACTOR)) else: # bit value has changed! if bit == 0: # jump to start of line to write START_Y = Y fo.write("G0 X%.4f Y%.4f\n" % (XMM, YMM)) else: # end of line to write if DIR > 0: E = E + (MM_PIXEL * (Y - START_Y)) else: E = E + (MM_PIXEL * (START_Y - Y)) fo.write("G1 X%.4f Y%.4f E%.4f\n" % (XMM, YMM, E * E_FACTOR)) last_bit = bit if CHANGE_DIRECTION: DIR = DIR * (-1) # change y direction on every X fo.write("""M400 ; Wait for all moves to finish M571 S0 E0 M42 P28 S0 ; Force laser off! M501 ; undo all settings made """) fo.close() if self.pronterwindow: self.log("") self.pronterwindow.load_gcode_async(filename + '.g') def hpgl2gcode(self, filename): OFFSET_X = 0.0 OFFSET_Y = 0.0 self.log("Converting HPGL plot for lasercut:") self.log("File: %s" % filename) fi = open(filename, "r") fo = open(filename + ".g", "w") fo.write(""" ; Filename: %s ; GCode generated by hpglplotter (marlin code flavour) G21 ; Metric ; We assume Z is in focus height and laser head is focus at bottom left of image! G92 X0 Y0 E0; set zero position - new origin G90 ; absolute positioning M82 ; Set extruder (laser) to absolute positioning M201 X1000 Y1000 E500 ; Set acceleration M203 X1000 Y1000 Z4 E10 ; Set max feedrate M209 S0 ; disable firmware retraction, we dont want to burn holes... M302 ; Allow cold extrudes - doesnt matter because we hack the extruder physically off with the M571 E mod M571 S1 E1 ; Activate Laser output on extrusion, but block real motor movement! G0 X0 Y0 F%d ; Set moving speed TRAVEL_SPEED G1 X0 Y0 F%d ; Set linear engraving speed ENGRAVE_SPEED """ % (filename, TRAVEL_SPEED, ENGRAVE_SPEED) ) G = "0" LASER_STATE = 0 last_coord = [0.0,0.0] last_cmd = "" for line in fi.readlines(): for action in line.split(";"): action = action.strip() if action != "": cmd = action[:2] if cmd == "PD": LASER_STATE = 1 elif cmd == "PU": LASER_STATE = 0 if last_cmd == "PD": OFFSET_X = coord[0] * -1 OFFSET_Y = coord[1] * -1 fo.write("; PD PU detected, set coord offset %.4f x %.4f mm\n" % (OFFSET_X, OFFSET_Y)) elif cmd == "PA" or cmd == "PR": # TODO: convert relative coordinates to absolute here! coord = action[2:].split(",") coord[0] = (float(coord[0]) + OFFSET_X) * SCALE_FACTOR coord[1] = (float(coord[1]) + OFFSET_Y) * SCALE_FACTOR if LASER_STATE: EN = " E%.4f F%.4f" % ( E_FACTOR * math.hypot(coord[0] - last_coord[0], coord[1] - last_coord[1]), ENGRAVE_SPEED * 0.5 ) # 1/2 engraving speed else: EN = " F%.4f" % TRAVEL_SPEED fo.write("G%d X%.4f Y%.4f%s\n" % ( LASER_STATE, coord[0], coord[1], EN) ) last_coord = coord elif cmd == "IN": pass elif cmd == "PT": print "Ignoring pen thickness" else: print "UNKNOWN: %s" % action last_cmd = cmd fo.write("""M400 ; Wait for all moves to finish M571 S0 E0 M42 P28 S0 ; Force laser off! M501 ; undo all settings made """) fi.close() fo.close() if self.pronterwindow: self.log("") self.pronterwindow.load_gcode_async(filename + '.g') def svg2gcode(self, filename, bed_max_x = 50, bed_max_y = 50, smoothness = 0.2): self.log("Generating paths from SVG...") preamble = """ ; Filename: %s ; GCode generated by bitplotter one-night-quick-hack script (marlin code flavour) ; 2015/2016 by NeoSoft - Malte Bayer G21 ; Metric ; We assume Z is in focus height and laser head is focus at bottom left of image! G92 X0 Y0 E0; set zero position - new origin G90 ; absolute positioning M82 ; Set extruder (laser) to absolute positioning M201 X1000 Y1000 E500 ; Set acceleration M203 X1000 Y1000 Z4 E10 ; Set max feedrate M209 S0 ; disable firmware retraction, we dont want to burn holes... M302 ; Allow cold extrudes - doesnt matter because we hack the extruder physically off with the M571 E mod M571 S1 E1 ; Activate Laser output on extrusion, but block real motor movement! G0 X0 Y0 F%d ; Set moving speed TRAVEL_SPEED G1 X0 Y0 F%d ; Set linear engraving speed ENGRAVE_SPEED """ % (filename, TRAVEL_SPEED, ENGRAVE_SPEED) self.log("Travel/Engrave speed: %d mm/sec, %d mm/sec" % ( TRAVEL_SPEED / 60, ENGRAVE_SPEED / 60) ) postamble = """M400 ; Wait for all moves to finish M571 S0 E0 M42 P28 S0 ; Force laser off! M501 ; undo all settings made """ shape_preamble = "G92 E0\n" shape_postamble = "" """ Used to control the smoothness/sharpness of the curves. Smaller the value greater the sharpness. Make sure the value is greater than 0.1 """ if smoothness < 0.1: smoothness = 0.1 svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path']) tree = ET.parse(filename) root = tree.getroot() width = root.get('width') height = root.get('height') if width == None or height == None: viewbox = root.get('viewBox') if viewbox: _, _, width, height = viewbox.split() if width == None or height == None: self.log("Unable to get width and height for the svg!") return False width = float(width.replace("px", "")) height = float(height.replace("px", "")) scale_x = bed_max_x / max(width, height) scale_y = bed_max_y / max(width, height) self.log("Scaling factor: %.2f, %.2f" % (scale_x,scale_y)) fo = open(filename + ".g", "w") fo.write(preamble) for elem in root.iter(): try: _, tag_suffix = elem.tag.split('}') except ValueError: continue if tag_suffix in svg_shapes: shape_class = getattr(shapes_pkg, tag_suffix) shape_obj = shape_class(elem) d = shape_obj.d_path() m = shape_obj.transformation_matrix() if d: fo.write("M400 ; wait for moves finish, then printing shape: %s\n" % (tag_suffix)) E = 0 xo = 0 yo = 0 fo.write(shape_preamble) p = point_generator(d, m, smoothness) start = True for x,y,pen in p: y = height - y xs = scale_x * x ys = scale_y * y if xo == xs and yo == ys: continue if not pen: start = True if xs >= 0 and xs <= bed_max_x and ys >= 0 and ys <= bed_max_y: if start: fo.write("G0 X%0.2f Y%0.2f F%.4f ; Move to start of shape\n" % (xs, ys, TRAVEL_SPEED)) start = False xo = xs yo = ys else: e_distance = math.hypot(xs - xo, ys - yo) xo = xs yo = ys E = E + (e_distance) fo.write("G1 X%0.2f Y%0.2f E%.4f F%.4f\n" % (xs, ys, E * E_FACTOR, ENGRAVE_SPEED)) else: self.log("Position outside print dimension: %d, %d" % (xs, ys)) fo.write(shape_postamble) fo.write(postamble) fo.close() if self.pronterwindow: self.log("") self.pronterwindow.load_gcode_async(filename + '.g')