diff --git a/README b/README
index a57906d4a327793100d5dfd82a7ed27c50938d23..5c9e04703799dd5023b7543b4140b05ad34c565f 100644
--- a/README
+++ b/README
@@ -1,8 +1,11 @@
 This is a library intended to provide suppport
 for Lasercutters on any platform.
 
-Currently it supports moste Epilog Lasers
-and the current LAOS board. (www.laoslaser.org).
+Currently it supports most Epilog Lasers,
+the current LAOS board. (www.laoslaser.org),
+the SmoothieBoard (www.smoothieware.org),
+generic GRBL based boards,
+and some untested work-in-progress drivers like the Roland iModela and the Lasersaur.
 
 It was created for VisiCut (http://visicut.org)
 but you are invited to use it for your own programs.
diff --git a/lib/corn-httpclient-1.0.12.jar b/lib/corn-httpclient-1.0.12.jar
new file mode 100644
index 0000000000000000000000000000000000000000..84f0b546db05da5a14dbe7c293757ad890fadf01
Binary files /dev/null and b/lib/corn-httpclient-1.0.12.jar differ
diff --git a/nbproject/project.properties b/nbproject/project.properties
index 7a20d16d240688e687da3d1cb1d61fdc64c0c63c..72619c653fade49c0e9b6c73811c01b443bf092c 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -44,6 +44,7 @@ dist.javadoc.dir=${dist.dir}/javadoc
 endorsed.classpath=
 excludes=
 file.reference.commons-net-3.1.jar=lib/commons-net-3.1.jar
+file.reference.corn-httpclient-1.0.12.jar=lib/corn-httpclient-1.0.12.jar
 file.reference.GLPKSolverPack.jar=lib/GLPKSolverPack.jar
 file.reference.jna-4.0.0.jar=lib/jna-4.0.0.jar
 file.reference.js.jar=lib/js.jar
@@ -59,7 +60,8 @@ javac.classpath=\
     ${file.reference.GLPKSolverPack.jar}:\
     ${file.reference.SCPSolver.jar}:\
     ${file.reference.jna-4.0.0.jar}:\
-    ${file.reference.purejavacomm-0.0.22.jar}
+    ${file.reference.purejavacomm-0.0.22.jar}:\
+    ${file.reference.corn-httpclient-1.0.12.jar}
 # Space-separated list of extra javac options
 javac.compilerargs=
 javac.deprecation=false
diff --git a/src/com/t_oster/liblasercut/FloatPowerSpeedFocusProperty.java b/src/com/t_oster/liblasercut/FloatPowerSpeedFocusProperty.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8ae027f3df0eed8f3c464c231e99c35726fbb89
--- /dev/null
+++ b/src/com/t_oster/liblasercut/FloatPowerSpeedFocusProperty.java
@@ -0,0 +1,237 @@
+/**
+ * This file is part of LibLaserCut.
+ * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de>
+ *
+ * LibLaserCut is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LibLaserCut is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **/
+package com.t_oster.liblasercut;
+
+/**
+ * The LaserProperty holds all the parameters for parts of the LaserJob.
+ * The Frequency value is ignored for Engraving operations
+ *
+ * @author oster
+ */
+public class FloatPowerSpeedFocusProperty implements LaserProperty
+{
+
+  private float power = 20;
+  private float speed = 100;
+  private float focus = 0;
+
+  public FloatPowerSpeedFocusProperty()
+  {
+  }
+
+  /**
+   * Sets the Laserpower. Valid values are from 0 to 100.
+   * In 3d-Raster mode, the intensity is scaled to this power setting
+   * @param power 
+   */
+  public void setPower(float power)
+  {
+    power = power < 0 ? 0 : power;
+    power = power > 100 ? 100 : power;
+    this.power = power;
+  }
+
+  public float getPower()
+  {
+    return power;
+  }
+
+  /**
+   * Sets the speed for the Laser. Valid values is from 0 to 100
+   * @param speed 
+   */
+  public void setSpeed(float speed)
+  {
+    speed = speed < 0 ? 0 : speed;
+    speed = speed > 100 ? 100 : speed;
+    this.speed = speed;
+  }
+
+  public float getSpeed()
+  {
+    return speed;
+  }
+
+  /**
+   * Sets the Focus aka moves the Z axis. Values are given in mm.
+   * Positive values move the Z axis down aka makes the distance between
+   * laser and object bigger.
+   * The possible range depends on the LaserCutter, so wrong setting
+   * may result in IllegalJobExceptions
+   * @param focus the relative Distance from object to Laser in mm
+   */
+  public void setFocus(float focus)
+  {
+    this.focus = focus;
+  }
+
+  /**
+   * Returns the relative (to the distance at starting the job) distance
+   * between laser and object in mm/10s
+   */
+  public float getFocus()
+  {
+    return this.focus;
+  }
+
+  @Override
+  public FloatPowerSpeedFocusProperty clone()
+  {
+    FloatPowerSpeedFocusProperty p = new FloatPowerSpeedFocusProperty();
+    p.focus = focus;
+    p.power = power;
+    p.speed = speed;
+    return p;
+  }
+
+  private static String[] propertyNames = new String[]{"power", "speed", "focus"};
+  
+  @Override
+  public String[] getPropertyKeys()
+  {
+    return propertyNames;
+  }
+
+  @Override
+  public Object getProperty(String name)
+  {
+    if ("power".equals(name))
+    {
+      return (Float) this.getPower();
+    }
+    else if ("speed".equals(name))
+    {
+      return (Float) this.getSpeed();
+    }
+    else if ("focus".equals(name))
+    {
+      return (Float) this.getFocus();
+    }
+    return null;
+  }
+
+  @Override
+  public void setProperty(String name, Object value)
+  {
+    if ("power".equals(name))
+    {
+      this.setPower((Float) value);
+    }
+    else if ("speed".equals(name))
+    {
+      this.setSpeed((Float) value);
+    }
+    else if ("focus".equals(name))
+    {
+      this.setFocus((Float) value);
+    }
+    else
+    {
+      throw new IllegalArgumentException("Unknown setting '"+name+"'");
+    }
+  }
+
+  @Override
+  public Object getMinimumValue(String name)
+  {
+  if ("power".equals(name))
+    {
+      return (Float) 0f;
+    }
+    else if ("speed".equals(name))
+    {
+      return (Float) 0f;
+    }
+    else if ("focus".equals(name))
+    {
+      return null;
+    }
+    else if ("frequency".equals(name))
+    {
+      return null;
+    }
+    else
+    {
+      throw new IllegalArgumentException("Unknown setting '"+name+"'");
+    }  
+  }
+
+  @Override
+  public Object getMaximumValue(String name)
+  {
+    if ("power".equals(name))
+    {
+      return (Float) 100f;
+    }
+    else if ("speed".equals(name))
+    {
+      return (Float) 100f;
+    }
+    else if ("focus".equals(name))
+    {
+      return null;
+    }
+    else if ("frequency".equals(name))
+    {
+      return null;
+    }
+    else
+    {
+      throw new IllegalArgumentException("Unknown setting '"+name+"'");
+    }
+  }  
+
+  @Override
+  public Object[] getPossibleValues(String name)
+  {
+    return null;
+  }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final FloatPowerSpeedFocusProperty other = (FloatPowerSpeedFocusProperty) obj;
+        if (Float.floatToIntBits(this.power) != Float.floatToIntBits(other.power)) {
+            return false;
+        }
+        if (Float.floatToIntBits(this.speed) != Float.floatToIntBits(other.speed)) {
+            return false;
+        }
+        if (Float.floatToIntBits(this.focus) != Float.floatToIntBits(other.focus)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 67 * hash + Float.floatToIntBits(this.power);
+        hash = 67 * hash + Float.floatToIntBits(this.speed);
+        hash = 67 * hash + Float.floatToIntBits(this.focus);
+        return hash;
+    }
+  
+
+}
diff --git a/src/com/t_oster/liblasercut/LaserCutter.java b/src/com/t_oster/liblasercut/LaserCutter.java
index e994c893dbc49dae307ce8b188d40840761b8f96..9fdbe43046a75881514cbdfbd6c46078172eed16 100644
--- a/src/com/t_oster/liblasercut/LaserCutter.java
+++ b/src/com/t_oster/liblasercut/LaserCutter.java
@@ -22,6 +22,7 @@
  */
 package com.t_oster.liblasercut;
 
+import com.t_oster.liblasercut.platform.Point;
 import com.t_oster.liblasercut.platform.Util;
 import java.util.LinkedList;
 import java.util.List;
@@ -173,6 +174,118 @@ public abstract class LaserCutter implements Cloneable, Customizable {
 
     public abstract String getModelName();
 
+    protected VectorPart convertRasterToVectorPart(RasterPart rp, LaserProperty blackPixelProperty, LaserProperty whitePixelProperty, double resolution, boolean unidirectional)
+    {
+      boolean dirRight = true;
+      Point rasterStart = rp.getRasterStart();
+      VectorPart result = new VectorPart(blackPixelProperty, resolution);
+      for (int line = 0; line < rp.getRasterHeight(); line++)
+      {
+        Point lineStart = rasterStart.clone();
+        lineStart.y += line;
+        //Convert BlackWhite line into line of 0 and 255 bytes
+        BlackWhiteRaster bwr = rp.image;
+        List<Byte> bytes = new LinkedList<Byte>();
+        boolean lookForStart = true;
+        for (int x = 0; x < bwr.getWidth(); x++)
+        {
+          if (lookForStart)
+          {
+            if (bwr.isBlack(x, line))
+            {
+              lookForStart = false;
+              bytes.add((byte) 255);
+            }
+            else
+            {
+              lineStart.x += 1;
+            }
+          }
+          else
+          {
+            bytes.add(bwr.isBlack(x, line) ? (byte) 255 : (byte) 0);
+          }
+        }
+        //remove trailing zeroes
+        while (bytes.size() > 0 && bytes.get(bytes.size() - 1) == 0)
+        {
+          bytes.remove(bytes.size() - 1);
+        }
+        if (bytes.size() > 0)
+        {
+          if (dirRight)
+          {
+            //move to the first nonempyt point of the line
+            result.moveto(lineStart.x, lineStart.y);
+            byte old = bytes.get(0);
+            for (int pix = 0; pix < bytes.size(); pix++)
+            {
+              if (bytes.get(pix) != old)
+              {
+                if (old == 0)
+                {//beginning of "black" segment -> move with 0 power
+                  result.setProperty(whitePixelProperty);
+                  result.lineto(lineStart.x + pix, lineStart.y);
+                }
+                else
+                {//end of "black" segment -> move with power to pixel before
+                  result.setProperty(blackPixelProperty);
+                  result.lineto(lineStart.x + pix - 1, lineStart.y);
+                }
+                old = bytes.get(pix);
+              }
+            }
+            result.setProperty(blackPixelProperty);
+            result.lineto(lineStart.x + bytes.size() - 1, lineStart.y);
+          }
+          else
+          {
+            //move to the last nonempty point of the line
+            result.moveto(lineStart.x + bytes.size() - 1, lineStart.y);
+            byte old = bytes.get(bytes.size() - 1);
+            for (int pix = bytes.size() - 1; pix >= 0; pix--)
+            {
+              if (bytes.get(pix) != old || pix == 0)
+              {
+                if (old == 0)
+                {
+                  result.setProperty(whitePixelProperty);
+                  result.lineto(lineStart.x + pix, lineStart.y);
+                }
+                else
+                {
+                  result.setProperty(blackPixelProperty);
+                  result.lineto(lineStart.x + pix + 1, lineStart.y);
+                }
+                old = bytes.get(pix);
+              }
+            }
+            //last pixel is always black (white pixels are stripped before)
+            result.setProperty(blackPixelProperty);
+            result.lineto(lineStart.x, lineStart.y);
+          }
+        }
+        if (!unidirectional)
+        {
+          dirRight = !dirRight;
+        }
+      }
+      return result;
+    }
+    
+    /**
+     * Intented for use in the clone mehtod. Copies all properties
+     * of that to this
+     * @param that 
+     */
+    protected void copyProperties(LaserCutter that)
+    {
+      for (String prop : that.getPropertyKeys())
+      {
+        setProperty(prop, that.getProperty(prop));
+      }
+    }
+    
     @Override
     public abstract LaserCutter clone();
 }
diff --git a/src/com/t_oster/liblasercut/LibInfo.java b/src/com/t_oster/liblasercut/LibInfo.java
index ec2f5705ddf12053f86c90ee3a80bd430668f1e9..2b7bcb40bcd8f3f25aafb7cb4b44aea944eebc86 100644
--- a/src/com/t_oster/liblasercut/LibInfo.java
+++ b/src/com/t_oster/liblasercut/LibInfo.java
@@ -45,7 +45,10 @@ public class LibInfo
       Dummy.class,
       IModelaMill.class,
       SampleDriver.class,
-      ExportSVG.class
+      ExportSVG.class,
+      GenericGcodeDriver.class,
+      SmoothieBoard.class,
+      Marlin.class
     };
   }
 }
diff --git a/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java b/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java
new file mode 100644
index 0000000000000000000000000000000000000000..a41cc9c9c3397de55eb2cb45a187a05a5fc5bb58
--- /dev/null
+++ b/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java
@@ -0,0 +1,828 @@
+/**
+ * This file is part of LibLaserCut.
+ * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de>
+ *
+ * LibLaserCut is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LibLaserCut is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **/
+package com.t_oster.liblasercut.drivers;
+
+import com.t_oster.liblasercut.*;
+import com.t_oster.liblasercut.platform.Util;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import purejavacomm.*;
+import java.util.*;
+import net.sf.corn.httpclient.HttpClient;
+import net.sf.corn.httpclient.HttpResponse;
+
+/**
+ * This class implements a driver for a generic GRBL GCode Lasercutter.
+ * It should contain all possible options and is inteded to be the superclass
+ * for e.g. the SmoothieBoard and the Lasersaur driver.
+ *
+ * @author Thomas Oster <thomas.oster@rwth-aachen.de>
+ */
+public class GenericGcodeDriver extends LaserCutter {
+
+  protected static final String SETTING_HOST = "IP/Hostname";
+  protected static final String SETTING_COMPORT = "COM Port";
+  protected static final String SETTING_BAUDRATE = "Baud Rate (Serial)";
+  protected static final String SETTING_BEDWIDTH = "Laserbed width";
+  protected static final String SETTING_BEDHEIGHT = "Laserbed height";
+  protected static final String SETTING_FLIP_X = "Flip X Axis";
+  protected static final String SETTING_FLIP_Y = "Flip Y Axis";
+  protected static final String SETTING_HTTP_UPLOAD_URL = "HTTP Upload URL";
+  protected static final String SETTING_LINEEND = "Lineend (CR,LF,CRLF)";
+  protected static final String SETTING_MAX_SPEED = "Max speed (in mm/min)";
+  protected static final String SETTING_TRAVEL_SPEED = "Travel (non laser moves) speed (in mm/min)";
+  protected static final String SETTING_PRE_JOB_GCODE = "Pre-Job GCode (comma separated)";
+  protected static final String SETTING_POST_JOB_GCODE = "Post-Job GCode (comma separated)";
+  protected static final String SETTING_RESOLUTIONS = "Supported DPI (comma separated)";
+  protected static final String SETTING_IDENTIFICATION_STRING = "Board Identification String (startsWith)";
+  protected static final String SETTING_WAIT_FOR_OK = "Wait for OK after each line (interactive mode)";
+  protected static final String SETTING_INIT_DELAY = "Seconds to wait for board reset (Serial)";
+  protected static final String SETTING_SERIAL_TIMEOUT = "Milliseconds to wait for response";
+  
+  private String lineend = "LF";
+
+  public String getLineend()
+  {
+    return lineend;
+  }
+
+  public void setLineend(String lineend)
+  {
+    this.lineend = lineend;
+  }
+  
+  protected String LINEEND()
+  {
+    return getLineend()
+      .replace("LF", "\n")
+      .replace("CR", "\r")
+      .replace("\\r", "\r")
+      .replace("\\n", "\n");
+  }
+
+  protected int baudRate = 115200;
+
+  public int getBaudRate()
+  {
+    return baudRate;
+  }
+
+  public void setBaudRate(int baudRate)
+  {
+    this.baudRate = baudRate;
+  }
+
+  protected boolean flipXaxis = false;
+
+  public boolean isFlipXaxis()
+  {
+    return flipXaxis;
+  }
+
+  public void setFlipXaxis(boolean flipXaxis)
+  {
+    this.flipXaxis = flipXaxis;
+  }
+  
+  protected boolean flipYaxis = false;
+
+  public boolean isFlipYaxis()
+  {
+    return flipYaxis;
+  }
+
+  public void setFlipYaxis(boolean flipYaxis)
+  {
+    this.flipYaxis = flipYaxis;
+  }
+  
+  protected String httpUploadUrl = "http://10.10.10.100/upload";
+
+  public String getHttpUploadUrl()
+  {
+    return httpUploadUrl;
+  }
+
+  public void setHttpUploadUrl(String httpUploadUrl)
+  {
+    this.httpUploadUrl = httpUploadUrl;
+  }
+  
+  protected String supportedResolutions = "100,500,1000";
+
+  public String getSupportedResolutions()
+  {
+    return supportedResolutions;
+  }
+
+  public void setSupportedResolutions(String supportedResolutions)
+  {
+    this.resolutions = null;
+    this.supportedResolutions = supportedResolutions;
+  }
+  
+  protected boolean waitForOKafterEachLine = true;
+
+  public boolean isWaitForOKafterEachLine()
+  {
+    return waitForOKafterEachLine;
+  }
+
+  public void setWaitForOKafterEachLine(boolean waitForOKafterEachLine)
+  {
+    this.waitForOKafterEachLine = waitForOKafterEachLine;
+  }
+
+  public String getIdentificationLine()
+  {
+    return identificationLine;
+  }
+
+  public void setIdentificationLine(String identificationLine)
+  {
+    this.identificationLine = identificationLine;
+  }
+  
+  protected String preJobGcode = "G21,G90";
+
+  public String getPreJobGcode()
+  {
+    return preJobGcode;
+  }
+
+  public void setPreJobGcode(String preJobGcode)
+  {
+    this.preJobGcode = preJobGcode;
+  }
+  
+  protected String postJobGcode = "G0 X0 Y0";
+
+  public String getPostJobGcode()
+  {
+    return postJobGcode;
+  }
+
+  public void setPostJobGcode(String postJobGcode)
+  {
+    this.postJobGcode = postJobGcode;
+  }
+  
+  protected int serialTimeout= 15000;
+
+  public int getSerialTimeout()
+  {
+    return serialTimeout;
+  }
+
+  public void setSerialTimeout(int serialTimeout)
+  {
+    this.serialTimeout = serialTimeout;
+  }
+  
+  /**
+   * What is expected to be received after serial/telnet connection
+   * Used e.g. for auto-detecting the serial port.
+   */
+  protected String identificationLine = "Grbl";
+  
+  @Override
+  public String getModelName() {
+    return "Generic GRBL GCode Driver";
+  }
+  
+  /**
+   * Time to wait before firsts reads of serial port.
+   * See autoreset feature on arduinos.
+   */
+  protected int initDelay = 5; 
+
+  public int getInitDelay()
+  {
+    return initDelay;
+  }
+
+  public void setInitDelay(int initDelay)
+  {
+    this.initDelay = initDelay;
+  }
+  
+  protected String host = "10.10.10.222";
+
+  public String getHost()
+  {
+    return host;
+  }
+
+  public void setHost(String host)
+  {
+    this.host = host;
+  }
+  
+  protected String comport = "auto";
+
+  public String getComport()
+  {
+    return comport;
+  }
+
+  public void setComport(String comport)
+  {
+    this.comport = comport;
+  }
+  
+  protected double max_speed = 20*60;
+
+  public double getMax_speed()
+  {
+    return max_speed;
+  }
+
+  public void setMax_speed(double max_speed)
+  {
+    this.max_speed = max_speed;
+  }
+  
+  protected double travel_speed = 60*60;
+
+  public double getTravel_speed()
+  {
+    return travel_speed;
+  }
+
+  public void setTravel_speed(double travel_speed)
+  {
+    this.travel_speed = travel_speed;
+  }
+  
+  @Override
+  /**
+   * We do not support Frequency atm, so we return power,speed and focus
+   */
+  public LaserProperty getLaserPropertyForVectorPart() {
+    return new FloatPowerSpeedFocusProperty();
+  }
+
+  @Override
+  public LaserProperty getLaserPropertyForRaster3dPart()
+  {
+    return new FloatPowerSpeedFocusProperty();
+  }
+
+  @Override
+  public LaserProperty getLaserPropertyForRasterPart()
+  {
+    return new FloatPowerSpeedFocusProperty();
+  }
+
+  protected void writeVectorGCode(VectorPart vp, double resolution) throws UnsupportedEncodingException, IOException {
+    for (VectorCommand cmd : vp.getCommandList()) {
+      switch (cmd.getType()) {
+        case MOVETO:
+          int x = cmd.getX();
+          int y = cmd.getY();
+          move(out, x, y, resolution);
+          break;
+        case LINETO:
+          x = cmd.getX();
+          y = cmd.getY();
+          line(out, x, y, resolution);
+          break;
+        case SETPROPERTY:
+          FloatPowerSpeedFocusProperty p = (FloatPowerSpeedFocusProperty) cmd.getProperty();
+          setPower(p.getPower());
+          setSpeed(p.getSpeed());
+          setFocus(out, p.getFocus(), resolution);
+          break;
+      }
+    }
+  }
+  private double currentPower = -1;
+  private double currentSpeed = -1;
+  private double nextPower = -1;
+  private double nextSpeed = -1;
+  private double currentFocus = 0;
+
+  protected void setSpeed(double speedInPercent) {
+    nextSpeed = speedInPercent;
+  }
+
+  protected void setPower(double powerInPercent) {
+    nextPower = powerInPercent;
+  }
+  
+  protected void setFocus(PrintStream out, double focus, double resolution) throws IOException {
+    if (currentFocus != focus)
+    {
+      sendLine("G0 Z%f", Util.px2mm(focus, resolution));
+      currentFocus = focus;
+    }
+  }
+
+  protected void move(PrintStream out, double x, double y, double resolution) throws IOException {
+    x = isFlipXaxis() ? getBedWidth() - Util.px2mm(x, resolution) : Util.px2mm(x, resolution);
+    y = isFlipYaxis() ? getBedHeight() - Util.px2mm(y, resolution) : Util.px2mm(y, resolution);
+    currentSpeed = getTravel_speed();
+    sendLine("G0 X%f Y%f F%d", x, y, (int) (travel_speed));
+  }
+
+  protected void line(PrintStream out, double x, double y, double resolution) throws IOException {
+    x = isFlipXaxis() ? getBedWidth() - Util.px2mm(x, resolution) : Util.px2mm(x, resolution);
+    y = isFlipYaxis() ? getBedHeight() - Util.px2mm(y, resolution) : Util.px2mm(y, resolution);
+    String append = "";
+    if (nextPower != currentPower)
+    {
+      append += String.format(Locale.US, " S%f", nextPower/100.0);
+      currentPower = nextPower;
+    }
+    if (nextSpeed != currentSpeed)
+    {
+      append += String.format(Locale.US, " F%d", (int) (max_speed*nextSpeed/100.0));
+      currentSpeed = nextSpeed;
+    }
+    sendLine("G1 X%f Y%f"+append, x, y);
+  }
+
+  private void writeInitializationCode() throws IOException {
+    if (preJobGcode != null)
+    {
+      for (String line : preJobGcode.split(","))
+      {
+        sendLine(line);
+      }
+    }
+  }
+
+  private void writeShutdownCode() throws IOException {
+    if (postJobGcode != null)
+    {
+      for (String line : postJobGcode.split(","))
+      {
+        sendLine(line);
+      }
+    }
+  }
+
+  private BufferedReader in;
+  private PrintStream out;
+  private Socket socket;
+  private CommPort port;
+  private CommPortIdentifier portIdentifier;
+  
+  protected void sendLine(String text, Object... parameters) throws IOException
+  {
+    out.format(text+LINEEND(), parameters);
+    //TODO: Remove
+    System.out.format("> "+text+LINEEND(), parameters);
+    out.flush();
+    if (isWaitForOKafterEachLine())
+    {
+      String line = waitForLine();
+      if (!"ok".equals(line))
+      {
+        throw new IOException("Lasercutter did not respond 'ok', but '"+line+"'instead.");
+      }
+    }
+  }
+  
+  protected void http_upload(URI url, String data, String filename) throws IOException
+  {
+    HttpClient client = new HttpClient(url);
+    client.putAdditionalRequestProperty("X-Filename", filename);
+    HttpResponse response = client.sendData(HttpClient.HTTP_METHOD.POST, data);
+    if (response == null || response.hasError())
+    {
+      throw new IOException("Error during POST Request");
+    }
+    System.out.println("Response: "+response.toString());//TODO: Remove
+  }
+  
+  protected String waitForLine() throws IOException
+  {
+    String line = "";
+    while ("".equals(line))
+    {//skip empty lines
+      line = in.readLine();
+    }
+    System.out.println("< "+line);//TODO: remove
+    return line;
+  }
+  
+  /**
+   * Waits for the Identification line and returns null if it's allright
+   * Otherwise it returns the wrong line
+   * @return
+   * @throws IOException 
+   */
+  protected String waitForIdentificationLine() throws IOException
+  {
+    if (getIdentificationLine() != null && getIdentificationLine().length() > 0)
+    {
+      String line = "";
+      for (int trials = 3; trials > 0; trials--)
+      {
+        line = waitForLine();
+        if (line.startsWith(getIdentificationLine()))
+        {
+          return null;
+        }
+      }
+      return line;
+    }
+    return null;
+  }
+  
+  protected String connect_serial(CommPortIdentifier i, ProgressListener pl) throws PortInUseException, IOException, UnsupportedCommOperationException
+  {
+    pl.taskChanged(this, "opening '"+i.getName()+"'");
+    if (i.getPortType() == CommPortIdentifier.PORT_SERIAL)
+    {
+      try
+      {
+        port = i.open("VisiCut", 1000);
+        try
+        {
+          port.enableReceiveTimeout(getSerialTimeout());
+        }
+        catch (UnsupportedCommOperationException e)
+        {
+          System.err.println("Serial timeout not supported. Driver may hang if device does not respond properly.");
+        }
+        if (this.getBaudRate() > 0 && port instanceof SerialPort)
+        {
+          SerialPort sp = (SerialPort) port;
+          sp.setSerialPortParams(getBaudRate(), 8, 1, 0);
+        }
+        out = new PrintStream(port.getOutputStream(), true, "US-ASCII");
+        in = new BufferedReader(new InputStreamReader(port.getInputStream()));
+        // Wait 5 seconds since GRBL is long to wake up..
+        for (int rest = getInitDelay(); rest > 0; rest--) {
+          pl.taskChanged(this, String.format("Waiting %ds", rest));
+          try
+          {
+            Thread.sleep(1000);
+          }
+          catch(InterruptedException ex)
+          {
+            Thread.currentThread().interrupt();
+          }
+        }
+        if (waitForIdentificationLine() != null)
+        {
+          in.close();
+          out.close();
+          port.close();
+          return "Does not seem to be a "+getModelName()+" on "+i.getName();
+        }
+        portIdentifier = i;
+        return null;
+      }
+      catch (PortInUseException e)
+      {
+        return "Port in use "+i.getName();
+      }
+      catch (IOException e)
+      {
+        return "IO Error "+i.getName();
+      }
+      catch (PureJavaIllegalStateException e)
+      {
+        return "Could not open "+i.getName();
+      }
+    }
+    else
+    {
+      return "Not a serial Port "+comport;
+    }
+  }
+  /**
+   * Used to buffer the file before uploading via http
+   */
+  private ByteArrayOutputStream outputBuffer;
+  protected void connect(ProgressListener pl) throws IOException, PortInUseException, NoSuchPortException, UnsupportedCommOperationException
+  {
+    outputBuffer = null;
+    if (getHost() != null && getHost().length() > 0)
+    {
+      socket = new Socket();
+      socket.connect(new InetSocketAddress(getHost(), 23), 1000);
+      in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+      out = new PrintStream(socket.getOutputStream(), true, "US-ASCII");
+      String line = waitForIdentificationLine();
+      if (line != null)
+      {
+        in.close();
+        out.close();
+        throw new IOException("Wrong identification Line: "+line+"\n instead of "+getIdentificationLine());
+      }
+    }
+    else if (getComport() != null && !getComport().equals(""))
+    {
+      String error = "No serial port found";
+      if (portIdentifier == null && !getComport().equals("auto"))
+      {
+        portIdentifier = CommPortIdentifier.getPortIdentifier(getComport());
+      }
+      
+      if (portIdentifier != null)
+      {//use port identifier we had last time
+        error = connect_serial(portIdentifier, pl);
+      }
+      else
+      {
+        Enumeration<CommPortIdentifier> e = CommPortIdentifier.getPortIdentifiers();
+        while (e.hasMoreElements())
+        {
+          CommPortIdentifier i = e.nextElement();
+          if (i.getPortType() == CommPortIdentifier.PORT_SERIAL)
+          {
+            error = connect_serial(i, pl);
+            if (error == null)
+            {
+              break;
+            }
+          }
+        }
+      }
+      if (error != null)
+      {
+        throw new IOException(error);
+      }
+    }
+    else if (getHttpUploadUrl() != null && getHttpUploadUrl().length() > 0)
+    {
+      outputBuffer = new ByteArrayOutputStream();
+      out = new PrintStream(outputBuffer);
+      setWaitForOKafterEachLine(false);
+      in = null;
+    }
+    else
+    {
+      throw new IOException("Either COM Port or IP/Host has to be set");
+    }
+  }
+  
+  protected void disconnect(String jobname) throws IOException, URISyntaxException
+  {
+    if (outputBuffer != null)
+    {
+      out.close();
+      http_upload(new URI(getHttpUploadUrl()), outputBuffer.toString("UTF-8"), jobname);
+    }
+    else
+    {
+      in.close();
+      out.close();
+      if (this.socket != null)
+      {
+        socket.close();
+        socket = null;
+      }
+      else if (this.port != null)
+      {
+        this.port.close();
+        this.port = null;
+      }   
+    }
+
+  }
+  
+  @Override
+  public void sendJob(LaserJob job, ProgressListener pl, List<String> warnings) throws IllegalJobException, Exception {
+    pl.progressChanged(this, 0);
+    this.currentPower = -1;
+    this.currentSpeed = -1;
+
+    pl.taskChanged(this, "checking job");
+    checkJob(job);
+    job.applyStartPoint();
+    pl.taskChanged(this, "connecting...");
+    connect(pl);
+    pl.taskChanged(this, "sending");
+    writeInitializationCode();
+    pl.progressChanged(this, 20);
+    int i = 0;
+    int max = job.getParts().size();
+    for (JobPart p : job.getParts())
+    {
+      if (p instanceof RasterPart)
+      {
+        RasterPart rp = (RasterPart) p;
+        LaserProperty black = rp.getLaserProperty();
+        LaserProperty white = black.clone();
+        white.setProperty("power", 0.0f);
+        p = convertRasterToVectorPart((RasterPart) p, black, white,  p.getDPI(), false);
+      }
+      if (p instanceof VectorPart)
+      {
+        //TODO: in direct mode use progress listener to indicate progress 
+        //of individual job
+        writeVectorGCode((VectorPart) p, p.getDPI());
+      }
+      i++;
+      pl.progressChanged(this, 20 + (int) (i*(double) 60/max));
+    }
+    writeShutdownCode();
+    disconnect(job.getName()+".gcode");
+    pl.taskChanged(this, "sent.");
+    pl.progressChanged(this, 100);
+  }
+  private List<Double> resolutions;
+
+  @Override
+  public List<Double> getResolutions() {
+    if (resolutions == null) {
+      resolutions = new LinkedList<Double>();
+      for (String s : getSupportedResolutions().split(","))
+      {
+        resolutions.add(Double.parseDouble(s));
+      }
+    }
+    return resolutions;
+  }
+  protected double bedWidth = 250;
+
+  /**
+   * Get the value of bedWidth
+   *
+   * @return the value of bedWidth
+   */
+  @Override
+  public double getBedWidth() {
+    return bedWidth;
+  }
+
+  /**
+   * Set the value of bedWidth
+   *
+   * @param bedWidth new value of bedWidth
+   */
+  public void setBedWidth(double bedWidth) {
+    this.bedWidth = bedWidth;
+  }
+  protected double bedHeight = 280;
+
+  /**
+   * Get the value of bedHeight
+   *
+   * @return the value of bedHeight
+   */
+  @Override
+  public double getBedHeight() {
+    return bedHeight;
+  }
+
+  /**
+   * Set the value of bedHeight
+   *
+   * @param bedHeight new value of bedHeight
+   */
+  public void setBedHeight(double bedHeight) {
+    this.bedHeight = bedHeight;
+  }
+  
+  private static String[] settingAttributes = new String[]{
+    SETTING_BAUDRATE,
+    SETTING_BEDWIDTH,
+    SETTING_BEDHEIGHT,
+    SETTING_COMPORT,
+    SETTING_FLIP_X,
+    SETTING_FLIP_Y,
+    SETTING_HOST,
+    SETTING_HTTP_UPLOAD_URL,
+    SETTING_IDENTIFICATION_STRING,
+    SETTING_INIT_DELAY,
+    SETTING_LINEEND,
+    SETTING_MAX_SPEED,
+    SETTING_TRAVEL_SPEED,
+    SETTING_PRE_JOB_GCODE,
+    SETTING_POST_JOB_GCODE,
+    SETTING_RESOLUTIONS,
+    SETTING_WAIT_FOR_OK,
+    SETTING_SERIAL_TIMEOUT
+  };
+
+  @Override
+  public String[] getPropertyKeys() {
+    return settingAttributes;
+  }
+
+  @Override
+  public Object getProperty(String attribute) {
+    if (SETTING_HOST.equals(attribute)) {
+      return this.getHost();
+    } else if (SETTING_BAUDRATE.equals(attribute)) {
+      return this.getBaudRate();
+    } else if (SETTING_BEDWIDTH.equals(attribute)) {
+      return this.getBedWidth();
+    } else if (SETTING_BEDHEIGHT.equals(attribute)) {
+      return this.getBedHeight();
+    } else if (SETTING_COMPORT.equals(attribute)) {
+      return this.getComport();
+    } else if (SETTING_FLIP_X.equals(attribute)) {
+      return this.isFlipXaxis();
+    } else if (SETTING_FLIP_Y.equals(attribute)) {
+      return this.isFlipYaxis();
+    } else if (SETTING_HOST.equals(attribute)) {
+      return this.getHost();
+    } else if (SETTING_HTTP_UPLOAD_URL.equals(attribute)) {
+      return this.getHttpUploadUrl();
+    } else if (SETTING_IDENTIFICATION_STRING.equals(attribute)) {
+      return this.getIdentificationLine();
+    } else if (SETTING_INIT_DELAY.equals(attribute)) {
+      return this.getInitDelay();
+    } else if (SETTING_LINEEND.equals(attribute)) {
+      return this.getLineend();
+    } else if (SETTING_MAX_SPEED.equals(attribute)) {
+      return this.getMax_speed();
+    } else if (SETTING_TRAVEL_SPEED.equals(attribute)) {
+      return this.getTravel_speed();
+    } else if (SETTING_PRE_JOB_GCODE.equals(attribute)) {
+      return this.getPreJobGcode();
+    } else if (SETTING_POST_JOB_GCODE.equals(attribute)) {
+      return this.getPostJobGcode();
+    } else if (SETTING_RESOLUTIONS.equals(attribute)) {
+      return this.getSupportedResolutions();
+    } else if (SETTING_WAIT_FOR_OK.equals(attribute)) {
+      return this.isWaitForOKafterEachLine();
+    } else if (SETTING_SERIAL_TIMEOUT.equals(attribute)) {
+      return this.getSerialTimeout();
+    }
+    
+    return null;
+  }
+
+  @Override
+  public void setProperty(String attribute, Object value) {
+    if (SETTING_HOST.equals(attribute)) {
+      this.setHost((String) value);
+    } else if (SETTING_BAUDRATE.equals(attribute)) {
+      this.setBaudRate((Integer) value);
+    } else if (SETTING_BEDWIDTH.equals(attribute)) {
+      this.setBedWidth((Double) value);
+    } else if (SETTING_BEDHEIGHT.equals(attribute)) {
+      this.setBedHeight((Double) value);
+    } else if (SETTING_COMPORT.equals(attribute)) {
+      this.setComport((String) value);
+    } else if (SETTING_FLIP_X.equals(attribute)) {
+      this.setFlipXaxis((Boolean) value);
+    } else if (SETTING_FLIP_Y.equals(attribute)) {
+      this.setFlipYaxis((Boolean) value);
+    } else if (SETTING_HOST.equals(attribute)) {
+      this.setHost((String) value);
+    } else if (SETTING_HTTP_UPLOAD_URL.equals(attribute)) {
+      this.setHttpUploadUrl((String) value);
+    } else if (SETTING_IDENTIFICATION_STRING.equals(attribute)) {
+      this.setIdentificationLine((String) value);
+    } else if (SETTING_INIT_DELAY.equals(attribute)) {
+      this.setInitDelay((Integer) value);
+    } else if (SETTING_LINEEND.equals(attribute)) {
+      this.setLineend((String) value);
+    } else if (SETTING_MAX_SPEED.equals(attribute)) {
+      this.setMax_speed((Double) value);
+    } else if (SETTING_TRAVEL_SPEED.equals(attribute)) {
+      this.setTravel_speed((Double) value);
+    } else if (SETTING_PRE_JOB_GCODE.equals(attribute)) {
+      this.setPreJobGcode((String) value);
+    } else if (SETTING_POST_JOB_GCODE.equals(attribute)) {
+      this.setPostJobGcode((String) value);
+    } else if (SETTING_RESOLUTIONS.equals(attribute)) {
+      this.setSupportedResolutions((String) value);
+    } else if (SETTING_WAIT_FOR_OK.equals(attribute)) {
+      this.setWaitForOKafterEachLine((Boolean) value);
+    } else if (SETTING_SERIAL_TIMEOUT.equals(attribute)) {
+      this.setSerialTimeout((Integer) value);
+    }
+  }
+
+  @Override
+  public GenericGcodeDriver clone() {
+    GenericGcodeDriver clone = new GenericGcodeDriver();
+    clone.copyProperties(this);
+    return clone;
+  }
+}
diff --git a/src/com/t_oster/liblasercut/drivers/Marlin.java b/src/com/t_oster/liblasercut/drivers/Marlin.java
new file mode 100644
index 0000000000000000000000000000000000000000..f900a7c9cfaed3ae585cbcd8e5514d8213eb1c70
--- /dev/null
+++ b/src/com/t_oster/liblasercut/drivers/Marlin.java
@@ -0,0 +1,110 @@
+/**
+ * This file is part of LibLaserCut.
+ * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de>
+ *
+ * LibLaserCut is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LibLaserCut is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **/
+package com.t_oster.liblasercut.drivers;
+
+import com.t_oster.liblasercut.drivers.GenericGcodeDriver;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class implements a driver for the laser cutter fork of Marlin.
+ * 
+ * @author quillford
+ */
+public class Marlin extends GenericGcodeDriver {
+
+  public Marlin()
+  {
+    //set some Marlin specific defaults
+    setIdentificationLine("start");
+    setWaitForOKafterEachLine(true);
+    setBaudRate(115200);
+    setLineend("CRLF");
+    setInitDelay(0);
+    setPreJobGcode(getPreJobGcode()+",G28 XY,M5");
+    setPostJobGcode(getPostJobGcode()+",M5,G28 XY");
+    setSerialTimeout(35000);
+    
+    //Marlin has no way to upload over the network so remove the upload url text
+    setHttpUploadUrl("");
+    setHost("");
+  }
+  
+  @Override
+  public String getIdentificationLine()
+  {
+    return("start");
+  }
+
+  @Override
+  public String[] getPropertyKeys()
+  {
+    List<String> result = new LinkedList<String>();
+    result.addAll(Arrays.asList(super.getPropertyKeys()));
+    result.remove(GenericGcodeDriver.SETTING_IDENTIFICATION_STRING);
+    result.remove(GenericGcodeDriver.SETTING_WAIT_FOR_OK);
+    result.remove(GenericGcodeDriver.SETTING_LINEEND);
+    result.remove(GenericGcodeDriver.SETTING_INIT_DELAY);
+    result.remove(GenericGcodeDriver.SETTING_HTTP_UPLOAD_URL);
+    result.remove(GenericGcodeDriver.SETTING_HOST);
+    return result.toArray(new String[0]);
+  }
+  
+  /**
+   * Waits for the Identification line and returns null if it's alright
+   * Otherwise it returns the wrong line
+   * @return
+   * @throws IOException 
+   */
+ @Override
+  protected String waitForIdentificationLine() throws IOException
+  {
+    if (getIdentificationLine() != null && getIdentificationLine().length() > 0)
+    {
+      String line = waitForLine();
+        if (line.startsWith(getIdentificationLine()))
+        {//we received the identification line ("start"), now we have to skip the rest of Marlin's dump
+          while(!(waitForLine().startsWith("echo:SD")))
+          {
+           //do nothing and wait until Marlin has dumped all of the settings
+          }
+          return null;
+        }
+    }
+    return null;
+  }
+
+
+  @Override
+  public String getModelName()
+  {
+    return "Marlin";
+  }
+
+  @Override
+  public Marlin clone()
+  {
+    Marlin clone = new Marlin();
+    clone.copyProperties(this);
+    return clone;
+  }
+
+}
\ No newline at end of file
diff --git a/src/com/t_oster/liblasercut/drivers/SmoothieBoard.java b/src/com/t_oster/liblasercut/drivers/SmoothieBoard.java
new file mode 100644
index 0000000000000000000000000000000000000000..6eef4cbcaa268fe6d8fedb2275b3c388e02af9de
--- /dev/null
+++ b/src/com/t_oster/liblasercut/drivers/SmoothieBoard.java
@@ -0,0 +1,97 @@
+/**
+ * This file is part of LibLaserCut.
+ * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de>
+ *
+ * LibLaserCut is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LibLaserCut is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **/
+package com.t_oster.liblasercut.drivers;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class implements a driver for SmoothieBoard.
+ * 
+ * @author Thomas Oster <mail@thomas-oster.de>
+ */
+public class SmoothieBoard extends GenericGcodeDriver {
+
+  public SmoothieBoard()
+  {
+    //set some smoothie-specific defaults
+    setIdentificationLine("Smoothie");
+    setWaitForOKafterEachLine(true);
+    setBaudRate(115200);
+    setLineend("CRLF");
+    setInitDelay(0);
+    setPreJobGcode(getPreJobGcode()+",M3");
+    setPostJobGcode(getPostJobGcode()+",M5");
+  }
+  
+  @Override
+  public String getIdentificationLine()
+  {
+    if (getHost() == null || "".equals(getHost()))
+    {
+      return "Smoothie";
+    }
+    else
+    {
+      return "Smoothie command shell";
+    }
+  }
+
+  @Override
+  protected String waitForLine() throws IOException
+  {
+    String line = super.waitForLine();
+    //The telnet interface for smoothie prepends lines with '> '
+    if (getHost() != null && !"".equals(getHost()) && line.startsWith("> "))
+    {
+      return line.substring(2);
+    }
+    return line;
+  }
+  
+  @Override
+  public String[] getPropertyKeys()
+  {
+    List<String> result = new LinkedList<String>();
+    result.addAll(Arrays.asList(super.getPropertyKeys()));
+    result.remove(GenericGcodeDriver.SETTING_IDENTIFICATION_STRING);
+    result.remove(GenericGcodeDriver.SETTING_WAIT_FOR_OK);
+    result.remove(GenericGcodeDriver.SETTING_BAUDRATE);
+    result.remove(GenericGcodeDriver.SETTING_LINEEND);
+    result.remove(GenericGcodeDriver.SETTING_INIT_DELAY);
+    return result.toArray(new String[0]);
+  }
+
+  @Override
+  public String getModelName()
+  {
+    return "Smoothie Board";
+  }
+
+  @Override
+  public SmoothieBoard clone()
+  {
+    SmoothieBoard clone = new SmoothieBoard();
+    clone.copyProperties(this);
+    return clone;
+  }
+
+}
\ No newline at end of file