printrun-src/printrun/laser.py

changeset 22
4c9bb8f93ae8
parent 21
8551b89bd05e
child 23
e18b2a4ef561
--- a/printrun-src/printrun/laser.py	Sat Jun 04 10:01:58 2016 +0200
+++ b/printrun-src/printrun/laser.py	Sat Jun 04 12:41:32 2016 +0200
@@ -6,32 +6,14 @@
 
 """
 LASERCUT SETTINGS
-TODO: move to printrun settings
+Will be overridden from pronterface 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
+from PIL import Image
+import sys
+import math
 
-"""
-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
 
 # GENERAL HEADER AND FOOTER GCODE
 GCODE_HEAD = """
@@ -48,27 +30,39 @@
 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
-
-""" % (TRAVEL_SPEED, ENGRAVE_SPEED)
+"""
 
 GCODE_FOOT = """G0 X0 Y0 F%.4f
 M400 ; Wait for all moves to finish
 M571 S0 E0
 M42 P28 S0 ; Force laser off!
 M501 ; undo all settings made
-""" % (TRAVEL_SPEED)
+""" % (100*60)
 
-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 LasercutterSettings:
+    """
+    Default settings object
+    """
+    def __init__(self):
+        self.lc_engrave_speed = 10
+        # 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
+        self.lc_travel_speed = 120
 
+        # BITMAP:
+        self.lc_bitmap_speed_factor = 1.0
+        self.lc_dpi = 300
+        self.lc_grey_threshold = 0
+        self.lc_change_dir = True
+        self.lc_invert_cut = True
+
+        # HPGL:
+        self.lc_hpgl_speed_factor = 1.0
+
+        # SVG:
+        self.lc_svg_speed_factor = 1.0
 
 class Lasercutter:
     """
@@ -78,24 +72,32 @@
     def __init__(self, pronterwindow = None):
         if pronterwindow:
             self.pronterwindow = pronterwindow
+            self.settings = pronterwindow.settings
             #self.log = pronterwindow.log
             self.log = self.log_print
             self.pronterwindow.clear_log(None)
         else:
             self.pronterwindow = None
+            self.settings = LasercutterSettings()
             self.log = lambda : None
-        self.log("Lasercutter library initialized\n%d DPI (%f mm/pixel)" % (DPI, MM_PIXEL))
-        if STEPS_PIXEL <= 5:
-            self.log("WARNING: STEPS PER PIXEL NEEDS TO BE > 5 (otherwise marlin joins lines): %f" % STEPS_PIXEL)
+
+        # STATIC DEFINITIONS, DO NOT CHANGE WORLD's RULES!
+        self.INCH = 25.4 # mm
+        self.MM_PIXEL = round(self.INCH / self.settings.lc_dpi, 4)
+        self.STEPS_PIXEL = self.MM_PIXEL * 80 # mine is 80 steps/mm on XY
+
+        self.log("Lasercutter library initialized\n%d DPI (%f mm/pixel)" % (self.settings.lc_dpi, self.MM_PIXEL))
+        if self.STEPS_PIXEL <= 5:
+            self.log("WARNING: STEPS PER PIXEL NEEDS TO BE > 5 (otherwise marlin joins lines): %f" % self.STEPS_PIXEL)
         self.log("Travel/Engrave speed: %d mm/sec, %d mm/sec" % (
-            TRAVEL_SPEED / 60, ENGRAVE_SPEED / 60) )
+            self.settings.lc_travel_speed, self.settings.lc_engrave_speed) )
         self.log("")
         
     def log_print(self, msg):
         print(msg)
 
     
-    def pixel2bit(self, pixel, threshold=128):
+    def pixel2bit(self, pixel):
         """Convert the pixel value to a bit."""
         # some really weird stuff here ;-P
 
@@ -105,14 +107,14 @@
         if isinstance(pixel, tuple):
             #rgb
             pixel = pixel[0]*0.2989 + pixel[1]*0.5870 + pixel[2]*0.1140
-            threshold = 128
-            if pixel > threshold:
+            if pixel > self.settings.lc_grey_threshold:
                 return 1
             else:
                 return 0
 
         # color palette
-        if pixel <= threshold:
+        # TODO: get the grey value of the palette index instead of using pixel which is the palette index?
+        if pixel <= self.settings.lc_grey_threshold:
             return 1
         else:
             return 0
@@ -140,12 +142,15 @@
         fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD))
 
         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) )
+            im.size[0], im.size[1], self.settings.lc_dpi, im.size[0] * self.MM_PIXEL, im.size[1] * self.MM_PIXEL) )
 
-        INVERT_Y = MM_PIXEL * (im.size[1] -1) * (-1)
+        INVERT_Y = self.MM_PIXEL * (im.size[1] -1) * (-1)
         DIR = 1
+        travel_speed = self.settings.lc_travel_speed * 60
+        engrave_speed = self.settings.lc_engrave_speed * 60 * self.settings.lc_bitmap_speed_factor
         for X in range(im.size[0]):
-            fo.write("; X=%d printing row: direction %i\n" % (X, DIR))
+            # TODO: Skip empty rows!!!
+            fo.write("M400 ; 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
@@ -158,11 +163,11 @@
                 range_stop = -1
 
             for Y in range(range_start, range_stop, DIR):
-                YMM = abs((Y * MM_PIXEL) + INVERT_Y)
-                XMM = X * MM_PIXEL
+                YMM = abs((Y * self.MM_PIXEL) + INVERT_Y)
+                XMM = X * self.MM_PIXEL
                 #print "X %d Y %d" % (X, Y)
-                bit = self.pixel2bit(pix[X, Y], GREY_THRESHOLD)
-                if INVERT_CUT:
+                bit = self.pixel2bit(pix[X, Y])
+                if self.settings.lc_invert_cut:
                     if bit == 0:
                         bit = 1
                     else:
@@ -177,25 +182,25 @@
                         if (Y == (im.size[1] - 1)) or (Y == 0):
                             # draw line
                             if DIR > 0:
-                                E = E + MM_PIXEL * (Y - START_Y)
+                                E = E + self.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))
+                                E = E + self.MM_PIXEL * (START_Y - Y)
+                            fo.write("G1 X%.4f Y%.4f E%.4f F%.4f\n" % (XMM, YMM, E * E_FACTOR, engrave_speed))
                 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))
+                        fo.write("G0 X%.4f Y%.4f F%.4f\n" % (XMM, YMM, travel_speed))
                     else:
                         # end of line to write
                         if DIR > 0:
-                            E = E + (MM_PIXEL * (Y - START_Y))
+                            E = E + (self.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))
+                            E = E + (self.MM_PIXEL * (START_Y - Y))
+                        fo.write("G1 X%.4f Y%.4f E%.4f F%.4f\n" % (XMM, YMM, E * E_FACTOR,  engrave_speed))
                 last_bit = bit
-            if CHANGE_DIRECTION:
+            if self.settings.lc_change_dir:
                 DIR = DIR * (-1) # change y direction on every X
 
         fo.write(GCODE_FOOT)
@@ -206,6 +211,8 @@
             self.pronterwindow.load_gcode_async(filename + '.g')
 
     def hpgl2gcode(self, filename):
+        # FOR HPGL:
+        SCALE_FACTOR = 1.0 / 40.0 # 40 plotter units
         OFFSET_X = 0.0
         OFFSET_Y = 0.0
 
@@ -221,6 +228,10 @@
         last_coord = [0.0,0.0] 
         last_cmd = ""
 
+        travel_speed = self.settings.lc_travel_speed * 60
+        engrave_speed = self.settings.lc_engrave_speed * 60 * self.settings.lc_hpgl_speed_factor
+
+
         for line in fi.readlines():
             for action in line.split(";"):
                 action = action.strip()
@@ -242,9 +253,9 @@
                         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
+                              engrave_speed)
                         else:
-                            EN = " F%.4f" % TRAVEL_SPEED
+                            EN = " F%.4f" % travel_speed
                             
                         fo.write("G%d X%.4f Y%.4f%s\n" % (
                             LASER_STATE, coord[0], coord[1], EN) )
@@ -265,22 +276,15 @@
             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...")        
-
-        shape_preamble = "G92 E0\n"
-        shape_postamble = ""
+        # Imports for SVG
+        import xml.etree.ElementTree as ET
+        from svg2gcode import shapes as shapes_pkg
+        from svg2gcode.shapes import point_generator
 
-        """ 
-        Used to control the smoothness/sharpness of the curves.
-        Smaller the value greater the sharpness. Make sure the
-        value is greater than 0.1
-        """
+        self.log("Generating paths from SVG, alternative lib (outlines only)...")        
         if smoothness < 0.1: smoothness = 0.1
-        
         svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path'])
-    
         tree = ET.parse(filename)
         root = tree.getroot()
     
@@ -295,8 +299,8 @@
             self.log("Unable to get width and height for the svg!")
             return False
 
-        width = float(width.replace("px", ""))
-        height = float(height.replace("px", ""))
+        width = float(width.replace("px", "").replace("pt", ""))
+        height = float(height.replace("px", "").replace("pt", ""))
 
         scale_x = bed_max_x / max(width, height)
         scale_y = bed_max_y / max(width, height)
@@ -306,6 +310,9 @@
         fo = open(filename + ".g", "w")
         fo.write("; Filename: %s\n%s" % (filename, GCODE_HEAD))
 
+        travel_speed = self.settings.lc_travel_speed * 60
+        engrave_speed = self.settings.lc_engrave_speed * 60 * self.settings.lc_svg_speed_factor
+
         for elem in root.iter():
             try:
                 _, tag_suffix = elem.tag.split('}')
@@ -319,11 +326,11 @@
                 m = shape_obj.transformation_matrix()
 
                 if d:
-                    fo.write("M400 ; wait for moves finish, then printing shape: %s\n" % (tag_suffix))
+                    fo.write("M400 ; start %s\n" % (tag_suffix))
+                    fo.write("G92 E0\n") 
                     E = 0
                     xo = 0
                     yo = 0
-                    fo.write(shape_preamble) 
                     p = point_generator(d, m, smoothness)
                     start = True
                     for x,y,pen in p:
@@ -335,7 +342,7 @@
                         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))
+                                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 
@@ -346,17 +353,15 @@
                                 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))
+                                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)) 
                     if shape_obj.xml_node.get('fill'):
                         # Close the polygon
                         e_distance = math.hypot(object_xs - xo, object_ys - yo)
                         E = E + (e_distance)
-                        fo.write("G1 X%0.2f Y%0.2f E%.4f F%.4f ; Close the object polygon\n" % (object_xs, object_ys, E * E_FACTOR, ENGRAVE_SPEED))
-                        print "connecting polycommon path end to start"
-
-                    fo.write(shape_postamble)
+                        fo.write("G1 X%0.2f Y%0.2f E%.4f F%.4f ; Close the object polygon\n" % (object_xs, object_ys, E * E_FACTOR, engrave_speed))
+                        print "connecting filled path end to start"
 
         fo.write(GCODE_FOOT)
         fo.close()

mercurial