diff --git a/modules/index.html b/modules/index.html
index 9b23872cc16f07c606bc4e2ece86ef2d398f4a95..b2c8f388271b146b52f7512af81e80cf7ee45fb5 100644
--- a/modules/index.html
+++ b/modules/index.html
@@ -92,6 +92,10 @@
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vinyl cutter</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/toolpath/machines/Roland/vinyl%20cutter/GX-24')">GX-24</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/toolpath/machines/ShopBot')">ShopBot</a><br>
+<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;laser cutter</i><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/toolpath/machines/laser%20cutter/Epilog')">Epilog</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/toolpath/machines/laser%20cutter/GCC')">GCC</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/toolpath/machines/laser%20cutter/Trotec')">Trotec</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/toolpath/view')">view</a><br>
 <i>ui</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('modules/ui/button')">button</a><br>
diff --git a/modules/toolpath/machines/laser cutter/Epilog b/modules/toolpath/machines/laser cutter/Epilog
new file mode 100644
index 0000000000000000000000000000000000000000..dc8daf6d80e195d31894780b6f876df1c31c3e3b
--- /dev/null
+++ b/modules/toolpath/machines/laser cutter/Epilog	
@@ -0,0 +1,173 @@
+//
+// Roland GX-24 vinyl cutter
+//
+// Neil Gershenfeld 
+// (c) Massachusetts Institute of Technology 2016
+// 
+// This work may be reproduced, modified, distributed, performed, and 
+// displayed for any purpose, but must acknowledge the mods
+// project. Copyright is retained and must be preserved. The work is 
+// provided as is; no warranty is provided, and users accept all 
+// liability.
+//
+// closure
+//
+(function(){
+//
+// module globals
+//
+var mod = {}
+//
+// name
+//
+var name = 'Roland GX-24 vinyl cutter'
+//
+// initialization
+//
+var init = function() {
+   mod.force.value = 50
+   mod.speed.value = 2
+   }
+//
+// inputs
+//
+var inputs = {
+   toolpath:{type:'object',
+      event:function(evt){
+         mod.name = evt.detail.name
+         mod.path = evt.detail.path
+         mod.dpi = evt.detail.dpi
+         mod.width = evt.detail.width
+         mod.height = evt.detail.height
+         make_path()
+         }}}
+//
+// outputs
+//
+var outputs = {
+   file:{type:'object',
+      event:function(str){
+         obj = {}
+         obj.type = 'file'
+         obj.name = mod.name+'.camm'
+         obj.contents = str
+         mods.output(mod,'file',obj)
+         }}}
+//
+// interface
+//
+var interface = function(div){
+   mod.div = div
+   div.appendChild(document.createTextNode('force (g): '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 6
+      div.appendChild(input)
+      mod.force = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('speed (cm/s): '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 6
+      div.appendChild(input)
+      mod.speed = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('origin:'))
+   div.appendChild(document.createElement('br'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'topleft'
+      div.appendChild(input)
+      mod.topleft = input
+   div.appendChild(document.createTextNode(' left \u00A0\u00A0 top \u00A0\u00A0 right '))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'topright'
+      div.appendChild(input)
+      mod.topright = input
+   div.appendChild(document.createElement('br'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'botleft'
+      input.checked = true
+      div.appendChild(input)
+      mod.botleft = input
+   div.appendChild(document.createTextNode(' left bottom right '))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'botright'
+      div.appendChild(input)
+      mod.botright = input
+   }
+//
+// local functions
+//
+function make_path() {
+   var dx = 25.4*mod.width/mod.dpi
+   var dy = 25.4*mod.height/mod.dpi
+   var nx = mod.width
+   var ny = mod.height
+   var force = parseFloat(mod.force.value)
+   var speed = parseFloat(mod.speed.value)
+   var str = "PA;PA;!ST1;!FS"+force+";VS"+speed+";\n"
+   var scale = 40.0*dx/(nx-1.0) // 40/mm
+   var ox = 0
+   var oy = 0
+   if (mod.botleft.checked) {
+      var xoffset = 40.0*ox
+      var yoffset = 40.0*oy
+      }
+   else if (mod.botright.checked) {
+      var xoffset = 40.0*(ox-dx)
+      var yoffset = 40.0*oy
+      }
+   else if (mod.topleft.checked) {
+      var xoffset = 40.0*ox
+      var yoffset = 40.0*(oy-dy)
+      }
+   else if (mod.topright.checked) {
+      var xoffset = 40.0*(ox-dx)
+      var yoffset = 40.0*(oy-dy)
+      }
+   //
+   // loop over segments
+   //
+   for (var seg = 0; seg < mod.path.length; ++seg) {
+      x = xoffset+scale*mod.path[seg][0][0]
+      y = yoffset+scale*mod.path[seg][0][1]
+      str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move up to start point
+      //str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move down
+      //str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      //
+      // loop over points
+      //
+      for (var pt = 1; pt < mod.path[seg].length; ++pt) {
+         x = xoffset+scale*mod.path[seg][pt][0]
+         y = yoffset+scale*mod.path[seg][pt][1]
+         str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move down
+         //str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+         }
+      str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move up at last point
+      //str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      }
+   str += "PU0,0;\n" // pen up to origin
+   //str += "PU0,0;\n" // hack: repeat in case comm dropped
+   outputs.file.event(str)
+   }
+
+//
+// return values
+//
+return ({
+   name:name,
+   init:init,
+   inputs:inputs,
+   outputs:outputs,
+   interface:interface
+   })
+}())
diff --git a/modules/toolpath/machines/laser cutter/GCC b/modules/toolpath/machines/laser cutter/GCC
new file mode 100644
index 0000000000000000000000000000000000000000..dc8daf6d80e195d31894780b6f876df1c31c3e3b
--- /dev/null
+++ b/modules/toolpath/machines/laser cutter/GCC	
@@ -0,0 +1,173 @@
+//
+// Roland GX-24 vinyl cutter
+//
+// Neil Gershenfeld 
+// (c) Massachusetts Institute of Technology 2016
+// 
+// This work may be reproduced, modified, distributed, performed, and 
+// displayed for any purpose, but must acknowledge the mods
+// project. Copyright is retained and must be preserved. The work is 
+// provided as is; no warranty is provided, and users accept all 
+// liability.
+//
+// closure
+//
+(function(){
+//
+// module globals
+//
+var mod = {}
+//
+// name
+//
+var name = 'Roland GX-24 vinyl cutter'
+//
+// initialization
+//
+var init = function() {
+   mod.force.value = 50
+   mod.speed.value = 2
+   }
+//
+// inputs
+//
+var inputs = {
+   toolpath:{type:'object',
+      event:function(evt){
+         mod.name = evt.detail.name
+         mod.path = evt.detail.path
+         mod.dpi = evt.detail.dpi
+         mod.width = evt.detail.width
+         mod.height = evt.detail.height
+         make_path()
+         }}}
+//
+// outputs
+//
+var outputs = {
+   file:{type:'object',
+      event:function(str){
+         obj = {}
+         obj.type = 'file'
+         obj.name = mod.name+'.camm'
+         obj.contents = str
+         mods.output(mod,'file',obj)
+         }}}
+//
+// interface
+//
+var interface = function(div){
+   mod.div = div
+   div.appendChild(document.createTextNode('force (g): '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 6
+      div.appendChild(input)
+      mod.force = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('speed (cm/s): '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 6
+      div.appendChild(input)
+      mod.speed = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('origin:'))
+   div.appendChild(document.createElement('br'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'topleft'
+      div.appendChild(input)
+      mod.topleft = input
+   div.appendChild(document.createTextNode(' left \u00A0\u00A0 top \u00A0\u00A0 right '))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'topright'
+      div.appendChild(input)
+      mod.topright = input
+   div.appendChild(document.createElement('br'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'botleft'
+      input.checked = true
+      div.appendChild(input)
+      mod.botleft = input
+   div.appendChild(document.createTextNode(' left bottom right '))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'botright'
+      div.appendChild(input)
+      mod.botright = input
+   }
+//
+// local functions
+//
+function make_path() {
+   var dx = 25.4*mod.width/mod.dpi
+   var dy = 25.4*mod.height/mod.dpi
+   var nx = mod.width
+   var ny = mod.height
+   var force = parseFloat(mod.force.value)
+   var speed = parseFloat(mod.speed.value)
+   var str = "PA;PA;!ST1;!FS"+force+";VS"+speed+";\n"
+   var scale = 40.0*dx/(nx-1.0) // 40/mm
+   var ox = 0
+   var oy = 0
+   if (mod.botleft.checked) {
+      var xoffset = 40.0*ox
+      var yoffset = 40.0*oy
+      }
+   else if (mod.botright.checked) {
+      var xoffset = 40.0*(ox-dx)
+      var yoffset = 40.0*oy
+      }
+   else if (mod.topleft.checked) {
+      var xoffset = 40.0*ox
+      var yoffset = 40.0*(oy-dy)
+      }
+   else if (mod.topright.checked) {
+      var xoffset = 40.0*(ox-dx)
+      var yoffset = 40.0*(oy-dy)
+      }
+   //
+   // loop over segments
+   //
+   for (var seg = 0; seg < mod.path.length; ++seg) {
+      x = xoffset+scale*mod.path[seg][0][0]
+      y = yoffset+scale*mod.path[seg][0][1]
+      str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move up to start point
+      //str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move down
+      //str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      //
+      // loop over points
+      //
+      for (var pt = 1; pt < mod.path[seg].length; ++pt) {
+         x = xoffset+scale*mod.path[seg][pt][0]
+         y = yoffset+scale*mod.path[seg][pt][1]
+         str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move down
+         //str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+         }
+      str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move up at last point
+      //str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      }
+   str += "PU0,0;\n" // pen up to origin
+   //str += "PU0,0;\n" // hack: repeat in case comm dropped
+   outputs.file.event(str)
+   }
+
+//
+// return values
+//
+return ({
+   name:name,
+   init:init,
+   inputs:inputs,
+   outputs:outputs,
+   interface:interface
+   })
+}())
diff --git a/modules/toolpath/machines/laser cutter/Trotec b/modules/toolpath/machines/laser cutter/Trotec
new file mode 100644
index 0000000000000000000000000000000000000000..dc8daf6d80e195d31894780b6f876df1c31c3e3b
--- /dev/null
+++ b/modules/toolpath/machines/laser cutter/Trotec	
@@ -0,0 +1,173 @@
+//
+// Roland GX-24 vinyl cutter
+//
+// Neil Gershenfeld 
+// (c) Massachusetts Institute of Technology 2016
+// 
+// This work may be reproduced, modified, distributed, performed, and 
+// displayed for any purpose, but must acknowledge the mods
+// project. Copyright is retained and must be preserved. The work is 
+// provided as is; no warranty is provided, and users accept all 
+// liability.
+//
+// closure
+//
+(function(){
+//
+// module globals
+//
+var mod = {}
+//
+// name
+//
+var name = 'Roland GX-24 vinyl cutter'
+//
+// initialization
+//
+var init = function() {
+   mod.force.value = 50
+   mod.speed.value = 2
+   }
+//
+// inputs
+//
+var inputs = {
+   toolpath:{type:'object',
+      event:function(evt){
+         mod.name = evt.detail.name
+         mod.path = evt.detail.path
+         mod.dpi = evt.detail.dpi
+         mod.width = evt.detail.width
+         mod.height = evt.detail.height
+         make_path()
+         }}}
+//
+// outputs
+//
+var outputs = {
+   file:{type:'object',
+      event:function(str){
+         obj = {}
+         obj.type = 'file'
+         obj.name = mod.name+'.camm'
+         obj.contents = str
+         mods.output(mod,'file',obj)
+         }}}
+//
+// interface
+//
+var interface = function(div){
+   mod.div = div
+   div.appendChild(document.createTextNode('force (g): '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 6
+      div.appendChild(input)
+      mod.force = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('speed (cm/s): '))
+   var input = document.createElement('input')
+      input.type = 'text'
+      input.size = 6
+      div.appendChild(input)
+      mod.speed = input
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('origin:'))
+   div.appendChild(document.createElement('br'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'topleft'
+      div.appendChild(input)
+      mod.topleft = input
+   div.appendChild(document.createTextNode(' left \u00A0\u00A0 top \u00A0\u00A0 right '))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'topright'
+      div.appendChild(input)
+      mod.topright = input
+   div.appendChild(document.createElement('br'))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'botleft'
+      input.checked = true
+      div.appendChild(input)
+      mod.botleft = input
+   div.appendChild(document.createTextNode(' left bottom right '))
+   var input = document.createElement('input')
+      input.type = 'radio'
+      input.name = mod.div.id+'origin'
+      input.id = mod.div.id+'botright'
+      div.appendChild(input)
+      mod.botright = input
+   }
+//
+// local functions
+//
+function make_path() {
+   var dx = 25.4*mod.width/mod.dpi
+   var dy = 25.4*mod.height/mod.dpi
+   var nx = mod.width
+   var ny = mod.height
+   var force = parseFloat(mod.force.value)
+   var speed = parseFloat(mod.speed.value)
+   var str = "PA;PA;!ST1;!FS"+force+";VS"+speed+";\n"
+   var scale = 40.0*dx/(nx-1.0) // 40/mm
+   var ox = 0
+   var oy = 0
+   if (mod.botleft.checked) {
+      var xoffset = 40.0*ox
+      var yoffset = 40.0*oy
+      }
+   else if (mod.botright.checked) {
+      var xoffset = 40.0*(ox-dx)
+      var yoffset = 40.0*oy
+      }
+   else if (mod.topleft.checked) {
+      var xoffset = 40.0*ox
+      var yoffset = 40.0*(oy-dy)
+      }
+   else if (mod.topright.checked) {
+      var xoffset = 40.0*(ox-dx)
+      var yoffset = 40.0*(oy-dy)
+      }
+   //
+   // loop over segments
+   //
+   for (var seg = 0; seg < mod.path.length; ++seg) {
+      x = xoffset+scale*mod.path[seg][0][0]
+      y = yoffset+scale*mod.path[seg][0][1]
+      str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move up to start point
+      //str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move down
+      //str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      //
+      // loop over points
+      //
+      for (var pt = 1; pt < mod.path[seg].length; ++pt) {
+         x = xoffset+scale*mod.path[seg][pt][0]
+         y = yoffset+scale*mod.path[seg][pt][1]
+         str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move down
+         //str += "PD"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+         }
+      str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // move up at last point
+      //str += "PU"+x.toFixed(0)+","+y.toFixed(0)+";\n" // hack: repeat in case comm dropped
+      }
+   str += "PU0,0;\n" // pen up to origin
+   //str += "PU0,0;\n" // hack: repeat in case comm dropped
+   outputs.file.event(str)
+   }
+
+//
+// return values
+//
+return ({
+   name:name,
+   init:init,
+   inputs:inputs,
+   outputs:outputs,
+   interface:interface
+   })
+}())