diff --git a/src/com/t_oster/liblasercut/drivers/Dummy.java b/src/com/t_oster/liblasercut/drivers/Dummy.java
index 8c27a0d56a9a3adec462c60f3abadb26ac049107..e0a3c0d986362571562e1882ae619e515f0e5017 100644
--- a/src/com/t_oster/liblasercut/drivers/Dummy.java
+++ b/src/com/t_oster/liblasercut/drivers/Dummy.java
@@ -21,6 +21,12 @@ package com.t_oster.liblasercut.drivers;
 
 import com.t_oster.liblasercut.*;
 import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.util.*;
 
 /**
@@ -33,26 +39,204 @@ public class Dummy extends LaserCutter {
   private static final String SETTING_BEDWIDTH = "Laserbed width";
   private static final String SETTING_BEDHEIGHT = "Laserbed height";
   private static final String SETTING_RUNTIME = "Fake estimated run-time in seconds (-1 to disable)";
+  private static final String SETTING_SVG_OUTDIR = "SVG Debug output directory (set empty to disable)";
+  
+  /**
+   * SVG output creator, mostly for testing vector-sorting
+   */
+  class SVGWriter  {
+    private double xPrev,xNow,yPrev,yNow;
+    private StringBuilder svg = new StringBuilder();
+    private boolean vectorPathActive=false;
+    private boolean partActive=false;
+    private int idCounter=0;
+    private int partCounter=0;
+    private LaserCutter cutter;
+    private double dpi;
+
+
+    public SVGWriter(LaserCutter cutter) {
+      this.cutter = cutter;
+    }
+
+    /**
+     * start a new JobPart
+     * @param title some string that will be included in the group ID
+     * @param dpi 
+     */
+    public void startPart(String title, double dpi) {
+      endPart();
+      partCounter += 1;
+      this.dpi=dpi;
+      this.partActive=true;
+      svg.append("<g style=\"fill:none;stroke:#000000;stroke-width:0.1mm;\" id=\"");
+      svg.append("visicut-part").append(partCounter).append("-");
+      svg.append(title.replaceAll("[^a-zA-Z0-9]","_"));
+      svg.append("\">\n");
+    }
+    
+    /**
+     * end a JobPart
+     */
+    public void endPart() {
+      moveTo(0,0); // end path
+      if (partActive) {
+        partActive=false;
+        svg.append("</g>\n");
+      }
+    }
+    
+    private void setLocation(int x, int y) {
+      xPrev=xNow;
+      yPrev=yNow;
+      double factor = 25.4/dpi; // convert units to millimeters
+      xNow=x*factor;
+      yNow=y*factor;
+    }
+    
+    /**
+     * move to somewhere with laser off
+     * @param x
+     * @param y 
+     */
+    void moveTo(int x, int y) {
+      setLocation(x,y);
+      if (vectorPathActive) {
+        // end the previous path
+        svg.append("\"/>\n");
+        vectorPathActive=false;
+      }
+    }
+
+    /**
+     * move to somewhere with laser on
+     * @param x
+     * @param y 
+     */
+    void lineTo(int x, int y) {
+      setLocation(x,y);
+      if (!partActive) {
+        throw new RuntimeException("lineTo called outside of a part!");
+      }
+      if (!vectorPathActive) {
+        // start a new path
+        vectorPathActive=true;
+        svg.append("<path id=\"visicut-").append(idCounter).append("\" d=\"M ");
+        idCounter += 1;
+        svg.append(xPrev).append(",").append(yPrev).append(" ");
+      }
+      svg.append(xNow).append(",").append(yNow).append(" ");
+    }
+
+    /**
+     * generate SVG output string and reset everything (delete all path data)
+     * @return 
+     */
+    private String getSVG() {
+      endPart();
+      svg.insert(0,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> \n"
+              + "<!-- Created by VisiCut Debug output -->\n"
+              + "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" "
+              + "xmlns=\"http://www.w3.org/2000/svg\" "
+              + "width=\"" + cutter.getBedWidth() + "mm\" "
+              + "height=\"" + cutter.getBedHeight() + "mm\" "
+              + "viewBox=\"0 0 " + cutter.getBedWidth() + " " + cutter.getBedHeight() + "\" "
+              + "version=\"1.1\" id=\"svg\"> \n");
+      svg.append("</svg>\n");
+      String result=svg.toString();
+      svg = new StringBuilder();
+      idCounter=0;
+      return result;
+    }
+    
+    /**
+     * store a String into a file
+     * @param path the filename
+     * @param str the content to be stored
+     */
+    private void storeString(String path, String str) {
+      try {
+        FileWriter f = new FileWriter(path);
+        BufferedWriter b = new BufferedWriter(f);
+        b.write(str);
+        b.close();
+      } catch (Exception e) {
+        System.out.println("Could not write debug SVG: Exception: " + e);
+      }
+    }
+    
+    /**
+     * store XHTML viewer to file
+     * @param path
+     * @param svgString 
+     */
+    void storeXHTML(String path, String svgString) {
+      BufferedReader br = null;
+      StringBuilder xhtml = new StringBuilder();
+      try {
+        InputStream stream = new Dummy().getClass().getResourceAsStream("resources/visicut-svg-output-viewer.xhtml");
+        StringBuilder s = new StringBuilder();
+        br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+        String line="";
+        while ((line=br.readLine()) != null) {
+          if (line.contains("<!-- REPLACE THIS WITH SVG -->")) {
+            // insert svg, but skip first line with <?xml...
+            line=svgString.substring(svgString.indexOf("\n"));
+          }
+          xhtml.append(line).append("\n");
+        }
+        storeString(path, xhtml.toString());
+      } catch (Exception e) {
+        System.out.println("could not store debug XHTML: " + e);
+      } finally {
+        try {
+          br.close();
+        } catch (IOException ex) {
+          System.out.println("could not close bufferedWriter when storing debug XHTML");
+        }
+      }
+    }
+    
+    void store(String directory) {
+      if (directory == null || directory.isEmpty()) {
+        System.out.println("Not writing debug SVG - no output directory set (edit lasercutter settings to change)");
+      } else {
+        String pathSVG=directory + "/visicut-debug.svg";
+        System.out.println("storing SVG debug output to "+pathSVG);
+        String svgString=getSVG();
+        storeString(pathSVG,svgString);
+        String pathXHTML=directory + "/visicut-svg-output-viewer.xhtml";
+        System.out.println("storing SVG debug output (XHTML viewer) to "+pathXHTML);
+        storeXHTML(pathXHTML, svgString);
+      }
+    }
+    
+  }
+  
   @Override
   public String getModelName() {
     return "Dummy";
   }
+  
+
 
   @Override
   public void sendJob(LaserJob job, ProgressListener pl) throws IllegalJobException, Exception {
     pl.progressChanged(this, 0);
-
+    
     BufferedOutputStream out;
     pl.taskChanged(this, "checking job");
     checkJob(job);
     job.applyStartPoint();
     pl.taskChanged(this, "sending");
     pl.taskChanged(this, "sent.");
+    SVGWriter svg = new SVGWriter(this); // SVG debug output
     System.out.println("dummy-driver got LaserJob: ");
     // TODO don't just print the parts and settins, but also the commands
     // TODO if you have too much time, also implement some preview output (svg animation???) - would be nice for testing optimisations
      for (JobPart p : job.getParts())
         {
+          svg.startPart(p.getClass().getSimpleName(), p.getDPI());
           if (p instanceof VectorPart)
           {
             System.out.println("VectorPart");
@@ -60,16 +244,25 @@ public class Dummy extends LaserCutter {
             {
               if (cmd.getType() == VectorCommand.CmdType.SETPROPERTY)
               {
+                
                 if (!(cmd.getProperty() instanceof PowerSpeedFocusFrequencyProperty))
                 {
                   throw new IllegalJobException("This driver expects Power,Speed,Frequency and Focus as settings");
                 }
                 System.out.println(((PowerSpeedFocusFrequencyProperty) cmd.getProperty()).toString());
+              } else if (cmd.getType() == VectorCommand.CmdType.LINETO) {
+                System.out.println("LINETO \t" + cmd.getX() + ", \t" + cmd.getY());
+                svg.lineTo(cmd.getX(),cmd.getY());
+              } else if (cmd.getType() == VectorCommand.CmdType.MOVETO) {
+                System.out.println("MOVETO \t" + cmd.getX() + ", \t" + cmd.getY());
+                svg.moveTo(cmd.getX(),cmd.getY());
               }
             }
+            
           }
           if (p instanceof RasterPart)
           {
+            // TODO add raster output for SVG debug output
             RasterPart rp = ((RasterPart) p);
             if (rp.getLaserProperty() != null && !(rp.getLaserProperty() instanceof PowerSpeedFocusProperty))
             {
@@ -89,6 +282,7 @@ public class Dummy extends LaserCutter {
           }
       }
       System.out.println("end of job.");
+      svg.store(svgOutdir);
       pl.progressChanged(this, 100);
   }
 
@@ -161,10 +355,14 @@ public class Dummy extends LaserCutter {
   public void setBedHeight(double bedHeight) {
     this.bedHeight = bedHeight;
   }
+  
+  public String svgOutdir="";
+  
   private static String[] settingAttributes = new String[]{
     SETTING_BEDWIDTH,
     SETTING_BEDHEIGHT,
-    SETTING_RUNTIME
+    SETTING_RUNTIME,
+    SETTING_SVG_OUTDIR
   };
 
   @Override
@@ -180,6 +378,8 @@ public class Dummy extends LaserCutter {
       return this.getBedHeight();
     } else if (SETTING_RUNTIME.equals(attribute)) {
       return this.fakeRunTime;
+    } else if (SETTING_SVG_OUTDIR.equals(attribute)) {
+      return this.svgOutdir;
     }
     return null;
   }
@@ -192,6 +392,8 @@ public class Dummy extends LaserCutter {
       this.setBedHeight((Double) value);
     } else if (SETTING_RUNTIME.equals(attribute)) {
        this.fakeRunTime=Integer.parseInt(value.toString());
+    } else if (SETTING_SVG_OUTDIR.equals(attribute)) {
+      this.svgOutdir=value.toString();
     }
 
   }
diff --git a/src/com/t_oster/liblasercut/drivers/resources/visicut-svg-output-viewer.xhtml b/src/com/t_oster/liblasercut/drivers/resources/visicut-svg-output-viewer.xhtml
new file mode 100644
index 0000000000000000000000000000000000000000..938338cecc7a9e9e4cc25ddebd5e4919dc88b952
--- /dev/null
+++ b/src/com/t_oster/liblasercut/drivers/resources/visicut-svg-output-viewer.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html 
+PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" 
+xmlns:svg="http://www.w3.org/2000/svg"
+xmlns:xlink="http://www.w3.org/1999/xlink">
+<head>
+<title>VisiCut SVG debug view</title>
+</head>
+<style>
+.done{stroke:black;}
+.todo{stroke:grey; fill:none;}
+</style>
+<script>
+<![CDATA[
+var maximum=0;
+function load(n) {
+	// get maximum
+	var i=0;
+	while (true) {
+		var obj=document.getElementById('visicut-'+i);
+		if (obj==null) {
+			// end of elements reached
+			break;
+		}
+		i++;
+		maximum=i;
+	}
+	document.getElementById('slider').max=maximum;
+}
+function showUntil(n) {
+	for (var i=0; i<n; i++) {
+		document.getElementById('visicut-'+i).setAttribute('class','done');
+	}
+	var i=n;
+	while (true) {
+		var obj=document.getElementById('visicut-'+i);
+		if (obj==null) {
+			// end of elements reached
+			break;
+		}
+		obj.setAttribute('class','todo');
+		i++;
+	}
+}
+]]>
+</script>
+<body onload="load()">
+
+<h1>VisiCut debug output</h1>
+<div style="display:block; width:100%;">
+  Slide to see individual steps. Please use this with Chrome or another browser that supports &lt;input type=&quot;range&quot;&gt; (not Firefox).<br/>
+Start <input id="slider" type="range" value="0" min="0" max="0" onchange="showUntil(this.value)" style="width:60%"/> End
+</div>
+
+
+<!-- REPLACE THIS WITH SVG -->
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/t_oster/liblasercut/platform/Rectangle.java b/src/com/t_oster/liblasercut/platform/Rectangle.java
new file mode 100644
index 0000000000000000000000000000000000000000..0caba687a9febbf00f4ee7817df8316ab1f5c413
--- /dev/null
+++ b/src/com/t_oster/liblasercut/platform/Rectangle.java
@@ -0,0 +1,152 @@
+/**
+ * This file is part of VisiCut.
+ * Copyright (C) 2012 Max Gaukler <development@maxgaukler.de>
+ * 
+ *     VisiCut 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.
+ * 
+ *    VisiCut 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 VisiCut.  If not, see <http://www.gnu.org/licenses/>.
+ **/
+package com.t_oster.liblasercut.platform;
+
+/**
+ * (not really compatible) replacement of java.awt.Rectangle, 
+ * This Rectangle cannot be "empty" - at minimum it needs to have one point.
+ * 
+ * @see Point
+ */
+public class Rectangle {
+  private int x1, x2, y1, y2;
+
+  /**
+   * construct a rectangle with the corners (x1,y1) and (x2,y2)
+   */
+  public Rectangle(int x1, int y1, int x2, int y2)
+  {
+    this.x1=Math.min(x1,x2);
+    this.x2=Math.max(x1,x2);
+    this.y1=Math.min(y1,y2);
+    this.y2=Math.max(y1,y2);
+  }
+  
+  /**
+   * Construct a rectangle with the corners p1 and p2
+   */
+  public Rectangle(Point p1, Point p2) {
+    this(p1.x,p1.y,p2.x,p2.y);
+  }
+  
+  /** 
+   * construct a rectangle with only one point p
+   */
+  public Rectangle(Point p) {
+    this(p,p);
+  }
+  
+  
+  /**
+   * add a point to the boundary of this rectangle
+   * use this iteratively to get the boundingBox
+   */
+  public void add (int x, int y) {
+    if (x<x1) {
+      this.x1=x;
+    } else if (x>x2) {
+      this.x2=x;
+    }
+    
+    if (y<y1) {
+      this.y1=y;
+    } else if (y>y2) {
+      this.y2=y;
+    }
+  }
+  
+  public void add(Point p) {
+    if (p != null) {
+      add(p.x, p.y);
+    }
+  }
+  
+  /**
+   * smallest X coordinate
+   * @return int
+   */
+  public int getXMin() {
+    return x1;
+  }
+  
+  /**
+   * greatest X coordinate
+   */
+  public int getXMax() {
+    return x2;
+  }
+  
+  /**
+   * smallest Y coordinate
+   */
+  public int getYMin() {
+    return y1;
+  }
+  
+  /**
+   * greatest Y coordinate
+   */
+  public int getYMax() {
+    return y2;
+  }
+  
+  /** 
+   * X interval from left to right
+   */
+  public Interval getXInterval() {
+    return new Interval(x1,x2);
+  }
+  
+  /** 
+   * Y interval from top to bottom
+   */
+  public Interval getYInterval() {
+    return new Interval(y1,y2);
+  }
+  
+  @Override
+  public String toString() {
+    return "Rectangle(x1="+x1+",y1="+y1+",x2="+x2+",y2="+y2+")";
+  }
+
+  @Override
+  public Rectangle clone()
+  {
+    return new Rectangle(x1,y1,x2,y2);
+  }
+  
+  /**
+   * check if this is inside of (or equal) another rectangle
+   * @param other
+   * @return true if this rectangle is equal to or inside of the other one
+   */
+  public boolean isInsideOf(Rectangle other) {
+    return this.getXInterval().isSubsetOf(other.getXInterval()) 
+                    && this.getYInterval().isSubsetOf(other.getYInterval());
+  }
+  
+  /**
+   * check if the intersection of this rectangle with another one is not empty
+   * @param other
+   * @return true if rectangles have at least one point in common
+   */
+  public boolean intersects(Rectangle other) {
+    return this.getXInterval().intersects(other.getXInterval())
+            && this.getYInterval().intersects(other.getYInterval());
+  }
+}
diff --git a/src/com/t_oster/liblasercut/utils/VectorOptimizer.java b/src/com/t_oster/liblasercut/utils/VectorOptimizer.java
index e66a4510ad4e5da1e8fd195a1da3b5494fdb69e8..7a999994acd9f8c6facd15065877f8ddbd28a9b3 100644
--- a/src/com/t_oster/liblasercut/utils/VectorOptimizer.java
+++ b/src/com/t_oster/liblasercut/utils/VectorOptimizer.java
@@ -4,6 +4,9 @@ import com.t_oster.liblasercut.LaserProperty;
 import com.t_oster.liblasercut.VectorCommand;
 import com.t_oster.liblasercut.VectorPart;
 import com.t_oster.liblasercut.platform.Point;
+import com.t_oster.liblasercut.platform.Rectangle;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -16,8 +19,8 @@ public class VectorOptimizer
   public enum OrderStrategy
   {
     FILE,
-    //INNER_FIRST,
-    NEAREST
+    NEAREST,
+    INNER_FIRST
   }
 
   class Element
@@ -39,11 +42,42 @@ public class VectorOptimizer
         moves = inv;
       }
     }
+    
+   
+    
     Point getEnd()
     {
       return moves.isEmpty() ? start : moves.get(moves.size()-1);
     }
+    
+    /** 
+     * compute bounding box of moves, including start point
+     * @return Rectangle 
+     */
+    Rectangle boundingBox() {
+      if (start == null) { // TODO may this happen?
+        return null;
+      }
+      Rectangle bb=new Rectangle(start.x,start.y,start.x,start.y);
+      for (Point p: moves) {
+        bb.add(p);
+      }
+      return bb;
+    }
+    
+    /**
+     * test if this Element represents a closed path (polygon)
+     * @return true if start equals end, false otherwise
+     */
+    boolean isClosedPath() {
+      if ((start == null) || moves.isEmpty()) {
+        return false;
+      }
+      return getEnd().equals(start);
+    }
   }
+  
+
 
   private OrderStrategy strategy = OrderStrategy.FILE;
 
@@ -166,6 +200,144 @@ public class VectorOptimizer
         }
         break;
       }
+      case INNER_FIRST: {
+        /** cut inside parts first, outside parts later
+         * this algorithm is very robust, it works even for unconnected paths that are split into individual lines (e.g. from some DXF imports)
+         * it is not completely perfect, as it only considers the bounding-box and not the individual path
+         * 
+         * see below for documentation of the inner workings
+         */
+        
+        // helper classes:
+        abstract class ElementValueComparator implements Comparator<Element> {
+          /**
+           * get one integer from the element
+           * order ascending by this integer
+           * inside objects should have the lowest values
+           */
+          abstract int getValue(Element e);
+          
+          /**
+           * compare by getValue()
+           */
+          @Override
+          public int compare(Element a, Element b) {            
+            Integer av = new Integer(getValue(a));
+            Integer bv = new Integer(getValue(b));
+            return av.compareTo(bv);
+          }
+          
+        }
+        
+        class XMinComparator extends ElementValueComparator {
+          // compare by XMin a>b
+          @Override
+          int getValue(Element e) {
+            return -e.boundingBox().getXMin();
+          }
+        }
+        
+        class YMinComparator extends ElementValueComparator {
+          // compare by YMin a>b
+          @Override
+          int getValue(Element e) {
+            return -e.boundingBox().getYMin();
+          }
+        }
+        
+        class XMaxComparator extends ElementValueComparator {
+          // compare by XMax a<b
+          @Override
+          int getValue(Element e) {
+            return e.boundingBox().getXMax();
+          }
+        }
+        
+        class YMaxComparator extends ElementValueComparator {
+          // compare by YMax a<b
+          @Override
+          int getValue(Element e) {
+            return e.boundingBox().getYMax();
+          }
+        }
+        result.addAll(e);
+        /**
+         * HEURISTIC:
+         * this algorithm is based on the following observation:
+         * let I and O be rectangles, I inside O
+         * for explanations, assume that:
+         * - the X-axis goes from left to right
+         * - the Y-axis goes from bottom to top
+         * 
+         *         ---------------- O: outside rectangle
+         *         |              |
+         *         |    ----      |
+         * y axis  |    |in| I    |
+         * ^       |    ----      |
+         * |       |              |
+         * |       ----------------
+         * |
+         *  ------> x axis
+         * 
+         * look at each border:
+         *  right border: I.getXMax() < O.getXMax()
+         *   left border: I.getXMin() > O.getXMin()
+         *    top border: I.getYMax() < O.getYMax()
+         * bottom border: I.getYMin() > O.getYMin()
+         * 
+         * If we now SORT BY ymax ASCENDING, ymin DESCENDING, xmax ASCENDING, xmin DESCENDING
+         *           (higher sorting priority listed first)
+         * we get the rectangles sorted inside-out:
+         * 1. I
+         * 2. O
+         * 
+         * Because we sort by four values, this still works if 
+         * the two rectangles start at the same corner and have the same width,
+         * but only differ in height.
+         *  
+         * If each rectangle is split into four separate lines
+         * (e.g. because of a bad DXF import),
+         * this still mostly works:
+         *  1. O: bottom line
+         *  2. I: bottom
+         *  3. I: top, left, right (both have same YMax, but top has a higher YMin)
+         *  4: O: top, left, right (both have same YMax, but top has a higher YMin)
+         * 
+         * TRADEOFFS AND LIMITATIONS:
+         * This algorithm does not work for paths that have the same bounding-box
+         * (e.g. a circle inscribed to a square)
+         * 
+         * For concave polygons with the same bounding-box,
+         * many simple Polygon-inside-Polygon algorithms also fail 
+         * (or have a useless definition of "inside" that matches the misbehaviour):
+         * Draw a concave polygon, remove one point at a concave edge.
+         * The resulting polygon is clearly outside the original, although every edge of it is inside the original!
+         * 
+         * FUTURE WORK:
+         * It would also be nice to sort intersecting polygons, where one polygon
+         * is "90% inside" and "10% outside" the other.
+         * Real-world example:_A circular hole at the border of a rectangle.
+         * Due to rounding errors, it may appear slightly outside the rectangle.
+         * Mathematically, it is neither fully inside nor fully outside, but the
+         * user clearly wants it to be counted as "inside".
+         * 
+         * POSSIBLE LIBRARIES:
+         * http://sourceforge.net/projects/geom-java/
+         * http://sourceforge.net/projects/jts-topo-suite
+         * 
+         * USEFUL METHODS:
+         * Element.isClosedPath()
+         */
+        
+        // do the work:
+        Collections.sort(result,new XMinComparator());
+        Collections.sort(result,new YMinComparator());
+        Collections.sort(result,new XMaxComparator());
+        Collections.sort(result,new YMaxComparator());
+        
+        // the result is now mostly sorted
+        // TODO somehow sort by intersecting area
+      }
     }
     return result;
   }