diff --git a/modules/path/formats/g-code b/modules/path/formats/g-code
index 32e2adc6276d81ed2e015af0727eb69a90ee93ad..70d170a36160548b20b29e68afc876089027fe26 100644
--- a/modules/path/formats/g-code
+++ b/modules/path/formats/g-code
@@ -37,10 +37,10 @@ var name = 'path to G-code'
 // initialization
 //
 var init = function() {
-   mod.cutspeed.value = '2.5'
-   mod.plungespeed.value = '2.5'
+   mod.cutspeed.value = '1'
+   mod.plungespeed.value = '1'
    mod.jogheight.value = '2'
-   mod.spindlespeed.value = '11000'
+   mod.spindlespeed.value = '10000'
    mod.tool.value = '1'
    mod.coolantoff.checked = true
    mod.formatMm.checked = true
@@ -224,14 +224,14 @@ function make_path() {
    str += "G80\n" // cancel canned cycles
    str += "G90\n" // absolute coordinates
    str += "G94\n" // feed/minute units
-   str += "T"+tool+"M06\n" // tool selection, tool change
+   //str += "T"+tool+"M06\n" // tool selection, tool change // some interpreters have trouble with this
    str += "F"+cut_speed.toFixed(4)+"\n" // feed rate
    str += "S"+spindle_speed+"\n" // spindle speed
    if (mod.coolanton.checked)
       str += "M08\n" // coolant on
    str += "G00Z"+jog_height.toFixed(4)+"\n" // move up before starting spindle
    str += "M03\n" // spindle on clockwise
-   str += "G04 P1\n" // give spindle 1 second to spin up
+   //str += "G04 P1000\n" // give spindle 1 second to spin up // some interpreters have trouble with this
    //
    // follow segments
    //
@@ -262,7 +262,8 @@ function make_path() {
    //
    // finish
    //
-   str += "G00Z"+jog_height.toFixed(4)+"\n" // move up before stopping spindle
+   str += "G00Z"+jog_height.toFixed(4)+"\n" // move up
+   str += "G00X0.0000Y0.0000"+"Z"+jog_height.toFixed(4)+"\n" // finish at origin
    str += "M05\n" // spindle stop
    if (mod.coolanton.checked)
       str += "M09\n" // coolant off
diff --git a/programs/machines/G-code/mill 2D PCB png b/programs/machines/G-code/mill 2D PCB png
index e255ff46e523394780586de26995bb980e9dd5b3..414131fdfb32d64f45842637407537815cd875fb 100644
--- a/programs/machines/G-code/mill 2D PCB png	
+++ b/programs/machines/G-code/mill 2D PCB png	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"197.61164764292596","left":"2318.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"814.6116476429258","left":"2776.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"684.611647642926","left":"2345.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"297.61164764292596","left":"2777.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"139.61164764292596","left":"163.81399947031065","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"330.61164764292596","left":"1806.8139994703104","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1034.6116476429258","left":"1136.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"255.61164764292596","left":"1159.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"800.6116476429258","left":"1850.8139994703104","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"175.61164764292596","left":"615.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.8622681794886853":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1022.9754468998394","left":"655.7533689104926","filename":"modules/file/save","inputs":{},"outputs":{}},"0.8764917270856811":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//           - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '2.5'\n   mod.plungespeed.value = '2.5'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '11000'\n   mod.tool.value = '1'\n   mod.coolantoff.checked = true\n   mod.formatMm.checked = true\n   mod.unitMinutes.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // inch or mm format\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n   div.appendChild(document.createElement('br'))\n   //\n   // second or minute rate units \n   //\n   div.appendChild(document.createTextNode('rate units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitSeconds'\n      input.checked = true\n      div.appendChild(input)\n      mod.unitSeconds = input\n   div.appendChild(document.createTextNode('second'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitMinutes'\n      div.appendChild(input)\n      mod.unitMinutes = input\n   div.appendChild(document.createTextNode('minute'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_height /= 25.4\n      }\n   if (mod.unitMinutes.checked) {\n      cut_speed *= 60\n      plunge_speed *= 60\n      }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n   str += \"G04 P1000\\n\" // give spindle 1 second to spin up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"554.8925438405239","left":"624.9482640838332","filename":"modules/path/formats/g-code","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8764917270856811\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8764917270856811\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8622681794886853\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"297.61164764292596","left":"2672.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"914.6116476429258","left":"3130.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"784.611647642926","left":"2699.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"397.61164764292596","left":"3131.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"239.61164764292596","left":"517.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"430.61164764292596","left":"2160.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1134.6116476429258","left":"1490.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"355.61164764292596","left":"1513.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"900.6116476429258","left":"2204.8139994703106","filename":"undefined","inputs":{},"outputs":{}},"0.8622681794886853":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1122.9754468998394","left":"1009.7533689104926","filename":"modules/file/save","inputs":{},"outputs":{}},"0.8764917270856811":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//           - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '2.5'\n   mod.plungespeed.value = '2.5'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '11000'\n   mod.tool.value = '1'\n   mod.coolantoff.checked = true\n   mod.formatMm.checked = true\n   mod.unitMinutes.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // inch or mm format\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n   div.appendChild(document.createElement('br'))\n   //\n   // second or minute rate units \n   //\n   div.appendChild(document.createTextNode('rate units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitSeconds'\n      input.checked = true\n      div.appendChild(input)\n      mod.unitSeconds = input\n   div.appendChild(document.createTextNode('second'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitMinutes'\n      div.appendChild(input)\n      mod.unitMinutes = input\n   div.appendChild(document.createTextNode('minute'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_height /= 25.4\n      }\n   if (mod.unitMinutes.checked) {\n      cut_speed *= 60\n      plunge_speed *= 60\n      }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n   str += \"G04 P1000\\n\" // give spindle 1 second to spin up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"654.8925438405239","left":"978.9482640838332","filename":"modules/path/formats/g-code","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"172.61164764292596","left":"971.8139994703106","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8764917270856811\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8764917270856811\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8622681794886853\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/G-code/mill 2D PCB svg b/programs/machines/G-code/mill 2D PCB svg
index 11ac6ae181e60e988800d50f4eba079d42abd77c..861edfb17c31e2331915ace42b91c0c767a10610 100644
--- a/programs/machines/G-code/mill 2D PCB svg	
+++ b/programs/machines/G-code/mill 2D PCB svg	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"179.58350726962306","left":"2719.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"796.5835072696229","left":"3177.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"666.5835072696229","left":"2746.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"279.58350726962306","left":"3178.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"312.58350726962306","left":"2207.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1016.5835072696229","left":"1537.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"237.58350726962306","left":"1560.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"782.5835072696229","left":"2251.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"157.58350726962306","left":"1016.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.8622681794886853":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1004.9473065265367","left":"1056.9337776993061","filename":"modules/file/save","inputs":{},"outputs":{}},"0.057765477464730264":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"121.03488793999406","left":"209.57239697090614","filename":"modules/read/svg","inputs":{},"outputs":{}},"0.5022735462618486":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '1000'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"227.69493521948414","left":"583.852199242571","filename":"modules/convert/svg/image","inputs":{},"outputs":{}},"0.9604471250218796":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//           - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '2.5'\n   mod.plungespeed.value = '2.5'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '11000'\n   mod.tool.value = '1'\n   mod.coolantoff.checked = true\n   mod.formatMm.checked = true\n   mod.unitMinutes.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // inch or mm format\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n   div.appendChild(document.createElement('br'))\n   //\n   // second or minute rate units \n   //\n   div.appendChild(document.createTextNode('rate units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitSeconds'\n      input.checked = true\n      div.appendChild(input)\n      mod.unitSeconds = input\n   div.appendChild(document.createTextNode('second'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitMinutes'\n      div.appendChild(input)\n      mod.unitMinutes = input\n   div.appendChild(document.createTextNode('minute'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_height /= 25.4\n      }\n   if (mod.unitMinutes.checked) {\n      cut_speed *= 60\n      plunge_speed *= 60\n      }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n   str += \"G04 P1000\\n\" // give spindle 1 second to spin up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"553.1683166499946","left":"1028.839479173399","filename":"modules/path/formats/g-code","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.057765477464730264\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8622681794886853\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"183.58350726962306","left":"2741.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"800.5835072696229","left":"3199.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"670.5835072696229","left":"2768.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"283.58350726962306","left":"3200.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"316.58350726962306","left":"2229.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1020.5835072696229","left":"1559.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"241.58350726962306","left":"1582.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"786.5835072696229","left":"2273.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.8622681794886853":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1008.9473065265367","left":"1078.9337776993061","filename":"modules/file/save","inputs":{},"outputs":{}},"0.057765477464730264":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"125.03488793999406","left":"231.57239697090614","filename":"modules/read/svg","inputs":{},"outputs":{}},"0.5022735462618486":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '1000'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"231.69493521948414","left":"605.852199242571","filename":"modules/convert/svg/image","inputs":{},"outputs":{}},"0.9604471250218796":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//           - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '2.5'\n   mod.plungespeed.value = '2.5'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '11000'\n   mod.tool.value = '1'\n   mod.coolantoff.checked = true\n   mod.formatMm.checked = true\n   mod.unitMinutes.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // inch or mm format\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n   div.appendChild(document.createElement('br'))\n   //\n   // second or minute rate units \n   //\n   div.appendChild(document.createTextNode('rate units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitSeconds'\n      input.checked = true\n      div.appendChild(input)\n      mod.unitSeconds = input\n   div.appendChild(document.createTextNode('second'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitMinutes'\n      div.appendChild(input)\n      mod.unitMinutes = input\n   div.appendChild(document.createTextNode('minute'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_height /= 25.4\n      }\n   if (mod.unitMinutes.checked) {\n      cut_speed *= 60\n      plunge_speed *= 60\n      }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n   str += \"G04 P1000\\n\" // give spindle 1 second to spin up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"557.1683166499946","left":"1050.839479173399","filename":"modules/path/formats/g-code","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"89.58350726962306","left":"1051.9944082591242","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.057765477464730264\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8622681794886853\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/Roland/mill/MDX-20/PCB png b/programs/machines/Roland/mill/MDX-20/PCB png
index f2851c16469a65115cc6fd6883800fbe8be6c2e0..c3eab56f6ca731f9bc361980690a3c1b107e38fa 100644
--- a/programs/machines/Roland/mill/MDX-20/PCB png	
+++ b/programs/machines/Roland/mill/MDX-20/PCB png	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1256.4464938782","left":"732.0262362855781","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1046.4464938782","left":"1669.026236285578","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"929.4464938782","left":"2092.026236285578","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1143.4464938782","left":"1253.026236285578","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1349.4464938782","left":"268.026236285578","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"909.4464938782","left":"935.0262362855781","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"157.44649387820004","left":"944.0262362855781","inputs":{},"outputs":{}},"0.6542976287965465":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"799.4464938782","left":"2545.026236285578","inputs":{},"outputs":{}},"0.8677372177580144":{"definition":"//\n// Roland MDX-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland MDX-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 40.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 0\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin x (mm)\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      //div.appendChild(input)\n      mod.oz = input\n   //div.appendChild(document.createTextNode(' (mm)'))\n   //div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = mod.units*parseFloat(mod.jz.value);\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move home and stop')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\";\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"366.44649387820004","left":"1389.2258866527297","inputs":{},"outputs":{}},"0.30171548718226804":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"401.44649387820004","left":"479.026236285578","inputs":{},"outputs":{}},"0.1552937657005955":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // flip button\n   //\n   var flip_btn = document.createElement('button')\n      flip_btn.style.padding = mods.ui.padding\n      flip_btn.style.margin = 1\n      flip_btn.appendChild(document.createTextNode('flip'))\n      flip_btn.addEventListener('click',function(){\n         flip_image()\n         })\n      div.appendChild(flip_btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// flip image\n//\nfunction flip_image() {\n   var blob = new Blob(['('+flip_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction flip_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var index = 0\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w/2; ++col) {\n            index = (h-1-row)*w*4+col*4\n            r_index = (h-1-row)*w*4+(w-1-col)*4\n            temp_1 = buf[index+0]\n            temp_2 = buf[index+1]\n            temp_3 = buf[index+2]\n            buf[index+0] \n               = buf[r_index+0] \n            buf[index+1] \n               = buf[r_index+1] \n            buf[index+2] \n               = buf[r_index+2] \n            buf[r_index+0] \n               = temp_1 \n            buf[r_index+1] \n               = temp_2 \n            buf[r_index+2] \n               = temp_3 \n            buf[index+3] = 255 \n            buf[r_index+3] = 255 \n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"151.37049971406543","left":"130.6387148042915","inputs":{},"outputs":{}},"0.7493144359985944":{"definition":"//\r\n// Python serial server module\r\n//\r\n// Neil Gershenfeld \r\n// 1/17/20\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'WebSocket Python serial'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address.value = '127.0.0.1'\n   mod.port.value = '1234'\n   mod.device.value = '/dev/ttyUSB0'\n   mod.baud.value = '9600'\n   mod.flow_dsrdtr.checked = true\n   mod.socket = null\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   transmit:{type:'string',\r\n      event:function(evt){\r\n         serial_send_string(evt.detail)\r\n         }\r\n      },\r\n   file:{type:'object',\r\n      event:function(evt){\r\n         if (evt.detail.type == 'command') {\r\n            mod.command = evt.detail\r\n            socket_send(JSON.stringify(mod.command))\r\n            }\r\n         else if (evt.detail.type == 'file') {\r\n            mod.job = evt.detail\r\n            mod.job.type = 'file'\r\n            mod.label.nodeValue = 'send file'\r\n            mod.labelspan.style.fontWeight = 'bold'\r\n            }\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   receive:{type:'character',\r\n      event:function(str){\r\n         mods.output(mod,'receive',str)}}}\r\n//\r\n\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // server\r\n   //\r\n   var a = document.createElement('a')\r\n      a.href = './py/serialserver.py'\r\n      a.innerHTML = 'serialserver.py:'\r\n      a.target = '_blank'\r\n   div.appendChild(a)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         socket_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         socket_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // address\r\n   //\r\n   div.appendChild(document.createTextNode('address: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.address = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // port\r\n   //\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.port = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // status\r\n   //\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // serial\r\n   //\r\n   div.appendChild(document.createTextNode('serial device:'))\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         serial_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         serial_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // device\r\n   //\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.device = input\r\n   div.appendChild(document.createElement('br'))   \r\n   //\r\n   // baud rate\r\n   //\r\n   div.appendChild(document.createTextNode('baud rate:'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.baud = input\r\n   div.appendChild(document.createElement('br'))   \r\n   //\r\n   // flow control\r\n   //\r\n   div.appendChild(document.createTextNode('flow control:'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'radio'\r\n      input.name = mod.div.id+'flow'\r\n      input.id = mod.div.id+'flow_none'\r\n      div.appendChild(input)\r\n      mod.flow_none = input\r\n   div.appendChild(document.createTextNode('none\\u00A0\\u00A0\\u00A0\\u00A0'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'radio'\r\n      input.name = mod.div.id+'flow'\r\n      input.id = mod.div.id+'flow_rtscts'\r\n      div.appendChild(input)\r\n      mod.flow_rtscts = input\r\n   div.appendChild(document.createTextNode('RTSCTS'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'radio'\r\n      input.name = mod.div.id+'flow'\r\n      input.id = mod.div.id+'flow_dsrdtr'\r\n      div.appendChild(input)\r\n      mod.flow_dsrdtr = input\r\n   div.appendChild(document.createTextNode('DSRDTR'))\r\n   div.appendChild(document.createElement('br'))   \r\n   //\r\n   // file button\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('waiting for file')\r\n            mod.label = text\r\n            span.appendChild(text)\r\n         mod.labelspan = span\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         if (mod.socket == null) {\r\n            mod.status.value = \"can't send, not open\"\r\n            }\r\n         else if (mod.label.nodeValue == 'send file') {\r\n            socket_send(JSON.stringify(mod.job))\r\n            //mod.label.nodeValue = 'cancel'\r\n\t    mod.label.nodeValue = 'sending ...'\r\n            }\r\n         else if (mod.label.nodeValue == 'cancel') {\r\n            mod.command = {}\r\n            mod.command.type = 'cancel'\r\n            socket_send(JSON.stringify(mod.command))\r\n            }\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n      mod.status.value = \"socket opened\"\r\n      serial_open()\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      mod.socket = null\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = event.data\r\n      outputs.receive.event(event.data)\r\n      if ((event.data == 'done') || (event.data == 'cancel') || (event.data.slice(0,5) == 'error')) {\r\n         mod.label.nodeValue = 'waiting for file'\r\n         mod.labelspan.style.fontWeight = 'normal'\r\n         }\r\n      }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"socket closed\"\r\n   mod.socket = null\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != null) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction serial_open() {\r\n   if (mod.socket == null) {\r\n      mod.status.value = \"socket not open\"\r\n      }\r\n   else {\r\n      var msg = {}\r\n      msg.type = 'open'\r\n      msg.device = mod.device.value\r\n      msg.baud = mod.baud.value\r\n      if (mod.flow_none.checked)\r\n         msg.flow = 'none'\r\n      else if (mod.flow_rtscts.checked)\r\n         msg.flow = 'rtscts'\r\n      else if (mod.flow_dsrdtr.checked)\r\n         msg.flow = 'dsrdtr'\r\n      mod.socket.send(JSON.stringify(msg))\r\n      }\r\n   }\r\nfunction serial_close() {\r\n   if (mod.socket == null) {\r\n      mod.status.value = \"socket not open\"\r\n      }\r\n   else {\r\n      var msg = {}\r\n      msg.type = 'close'\r\n      msg.device = mod.device.value\r\n      mod.socket.send(JSON.stringify(msg))\r\n      }\r\n   }\r\nfunction serial_send_string(str) {\r\n   if (mod.socket == null) {\r\n      mod.status.value = \"socket not open\"\r\n      }\r\n   else {\r\n      var msg = {}\r\n      msg.type = 'string'\r\n      msg.string = str\r\n      mod.socket.send(JSON.stringify(msg))\r\n      mod.status.value = 'transmit'\r\n      }\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"162.49349595263885","left":"1886.2182877924663","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.30171548718226804\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.1552937657005955\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.1552937657005955\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7493144359985944\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
\ No newline at end of file
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1206.4464938782","left":"732.0262362855781","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"996.4464938782","left":"1669.026236285578","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"879.4464938782","left":"2092.026236285578","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1093.4464938782","left":"1253.026236285578","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1299.4464938782","left":"268.026236285578","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"859.4464938782","left":"935.0262362855781","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"107.44649387820004","left":"944.0262362855781","filename":"undefined","inputs":{},"outputs":{}},"0.6542976287965465":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"749.4464938782","left":"2545.026236285578","filename":"undefined","inputs":{},"outputs":{}},"0.8677372177580144":{"definition":"//\n// Roland MDX-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland MDX-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 40.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 0\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin x (mm)\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      //div.appendChild(input)\n      mod.oz = input\n   //div.appendChild(document.createTextNode(' (mm)'))\n   //div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = mod.units*parseFloat(mod.jz.value);\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move home and stop')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\";\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"316.44649387820004","left":"1389.2258866527297","filename":"undefined","inputs":{},"outputs":{}},"0.1552937657005955":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // flip button\n   //\n   var flip_btn = document.createElement('button')\n      flip_btn.style.padding = mods.ui.padding\n      flip_btn.style.margin = 1\n      flip_btn.appendChild(document.createTextNode('flip'))\n      flip_btn.addEventListener('click',function(){\n         flip_image()\n         })\n      div.appendChild(flip_btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// flip image\n//\nfunction flip_image() {\n   var blob = new Blob(['('+flip_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction flip_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var index = 0\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w/2; ++col) {\n            index = (h-1-row)*w*4+col*4\n            r_index = (h-1-row)*w*4+(w-1-col)*4\n            temp_1 = buf[index+0]\n            temp_2 = buf[index+1]\n            temp_3 = buf[index+2]\n            buf[index+0] \n               = buf[r_index+0] \n            buf[index+1] \n               = buf[r_index+1] \n            buf[index+2] \n               = buf[r_index+2] \n            buf[r_index+0] \n               = temp_1 \n            buf[r_index+1] \n               = temp_2 \n            buf[r_index+2] \n               = temp_3 \n            buf[index+3] = 255 \n            buf[r_index+3] = 255 \n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"101.37049971406543","left":"130.6387148042915","filename":"undefined","inputs":{},"outputs":{}},"0.7493144359985944":{"definition":"//\r\n// Python serial server module\r\n//\r\n// Neil Gershenfeld \r\n// 1/17/20\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'WebSocket Python serial'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address.value = '127.0.0.1'\n   mod.port.value = '1234'\n   mod.device.value = '/dev/ttyUSB0'\n   mod.baud.value = '9600'\n   mod.flow_dsrdtr.checked = true\n   mod.socket = null\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   transmit:{type:'string',\r\n      event:function(evt){\r\n         serial_send_string(evt.detail)\r\n         }\r\n      },\r\n   file:{type:'object',\r\n      event:function(evt){\r\n         if (evt.detail.type == 'command') {\r\n            mod.command = evt.detail\r\n            socket_send(JSON.stringify(mod.command))\r\n            }\r\n         else if (evt.detail.type == 'file') {\r\n            mod.job = evt.detail\r\n            mod.job.type = 'file'\r\n            mod.label.nodeValue = 'send file'\r\n            mod.labelspan.style.fontWeight = 'bold'\r\n            }\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   receive:{type:'character',\r\n      event:function(str){\r\n         mods.output(mod,'receive',str)}}}\r\n//\r\n\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // server\r\n   //\r\n   var a = document.createElement('a')\r\n      a.href = './py/serialserver.py'\r\n      a.innerHTML = 'serialserver.py:'\r\n      a.target = '_blank'\r\n   div.appendChild(a)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         socket_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         socket_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // address\r\n   //\r\n   div.appendChild(document.createTextNode('address: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.address = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // port\r\n   //\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.port = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // status\r\n   //\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // serial\r\n   //\r\n   div.appendChild(document.createTextNode('serial device:'))\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         serial_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         serial_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // device\r\n   //\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.device = input\r\n   div.appendChild(document.createElement('br'))   \r\n   //\r\n   // baud rate\r\n   //\r\n   div.appendChild(document.createTextNode('baud rate:'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.baud = input\r\n   div.appendChild(document.createElement('br'))   \r\n   //\r\n   // flow control\r\n   //\r\n   div.appendChild(document.createTextNode('flow control:'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'radio'\r\n      input.name = mod.div.id+'flow'\r\n      input.id = mod.div.id+'flow_none'\r\n      div.appendChild(input)\r\n      mod.flow_none = input\r\n   div.appendChild(document.createTextNode('none\\u00A0\\u00A0\\u00A0\\u00A0'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'radio'\r\n      input.name = mod.div.id+'flow'\r\n      input.id = mod.div.id+'flow_rtscts'\r\n      div.appendChild(input)\r\n      mod.flow_rtscts = input\r\n   div.appendChild(document.createTextNode('RTSCTS'))\r\n   div.appendChild(document.createElement('br'))   \r\n   var input = document.createElement('input')\r\n      input.type = 'radio'\r\n      input.name = mod.div.id+'flow'\r\n      input.id = mod.div.id+'flow_dsrdtr'\r\n      div.appendChild(input)\r\n      mod.flow_dsrdtr = input\r\n   div.appendChild(document.createTextNode('DSRDTR'))\r\n   div.appendChild(document.createElement('br'))   \r\n   //\r\n   // file button\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('waiting for file')\r\n            mod.label = text\r\n            span.appendChild(text)\r\n         mod.labelspan = span\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         if (mod.socket == null) {\r\n            mod.status.value = \"can't send, not open\"\r\n            }\r\n         else if (mod.label.nodeValue == 'send file') {\r\n            socket_send(JSON.stringify(mod.job))\r\n            //mod.label.nodeValue = 'cancel'\r\n\t    mod.label.nodeValue = 'sending ...'\r\n            }\r\n         else if (mod.label.nodeValue == 'cancel') {\r\n            mod.command = {}\r\n            mod.command.type = 'cancel'\r\n            socket_send(JSON.stringify(mod.command))\r\n            }\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n      mod.status.value = \"socket opened\"\r\n      serial_open()\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      mod.socket = null\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = event.data\r\n      outputs.receive.event(event.data)\r\n      if ((event.data == 'done') || (event.data == 'cancel') || (event.data.slice(0,5) == 'error')) {\r\n         mod.label.nodeValue = 'waiting for file'\r\n         mod.labelspan.style.fontWeight = 'normal'\r\n         }\r\n      }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"socket closed\"\r\n   mod.socket = null\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != null) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction serial_open() {\r\n   if (mod.socket == null) {\r\n      mod.status.value = \"socket not open\"\r\n      }\r\n   else {\r\n      var msg = {}\r\n      msg.type = 'open'\r\n      msg.device = mod.device.value\r\n      msg.baud = mod.baud.value\r\n      if (mod.flow_none.checked)\r\n         msg.flow = 'none'\r\n      else if (mod.flow_rtscts.checked)\r\n         msg.flow = 'rtscts'\r\n      else if (mod.flow_dsrdtr.checked)\r\n         msg.flow = 'dsrdtr'\r\n      mod.socket.send(JSON.stringify(msg))\r\n      }\r\n   }\r\nfunction serial_close() {\r\n   if (mod.socket == null) {\r\n      mod.status.value = \"socket not open\"\r\n      }\r\n   else {\r\n      var msg = {}\r\n      msg.type = 'close'\r\n      msg.device = mod.device.value\r\n      mod.socket.send(JSON.stringify(msg))\r\n      }\r\n   }\r\nfunction serial_send_string(str) {\r\n   if (mod.socket == null) {\r\n      mod.status.value = \"socket not open\"\r\n      }\r\n   else {\r\n      var msg = {}\r\n      msg.type = 'string'\r\n      msg.string = str\r\n      mod.socket.send(JSON.stringify(msg))\r\n      mod.status.value = 'transmit'\r\n      }\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"112.49349595263885","left":"1886.2182877924663","filename":"undefined","inputs":{},"outputs":{}},"0.30171548718226804":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"240.44649387820004","left":"493.026236285578","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.1552937657005955\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.1552937657005955\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7493144359985944\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.30171548718226804\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/Roland/mill/MDX-20/PCB svg b/programs/machines/Roland/mill/MDX-20/PCB svg
index 5a8b7a680fe08c39fb02d00f846d72bc1e3f1202..a146b81baf3ee89f4a184bfef60109e314b357f2 100644
--- a/programs/machines/Roland/mill/MDX-20/PCB svg	
+++ b/programs/machines/Roland/mill/MDX-20/PCB svg	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1262","left":"972","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1052","left":"1909","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"935","left":"2332","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1149","left":"1493","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1355","left":"508","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"915","left":"1175","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"163","left":"1184","inputs":{},"outputs":{}},"0.6542976287965465":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"805","left":"2785","inputs":{},"outputs":{}},"0.8677372177580144":{"definition":"//\n// Roland MDX-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland MDX-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 40.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 0\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin x (mm)\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      //div.appendChild(input)\n      mod.oz = input\n   //div.appendChild(document.createTextNode(' (mm)'))\n   //div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = mod.units*parseFloat(mod.jz.value);\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move home and stop')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\";\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"372","left":"1581","inputs":{},"outputs":{}},"0.7834289556361769":{"definition":"//\n// serial server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket serial'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'ttyUSB0'\n   mod.baud.value = 9600\n   mod.flow_rtscts.checked = true\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   transmit:{type:'string',\n      event:function(evt){\n         serial_send_string(evt.detail)\n         }\n      },\n   file:{type:'object',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.job.type = 'file'\n            mod.label.nodeValue = 'send file'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   receive:{type:'character',\n      event:function(str){\n         mods.output(mod,'receive',str)}}}\n//\n\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/serialserver.js'\n      a.innerHTML = 'serialserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // address\n   //\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   //\n   // port\n   //\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   //\n   // status\n   //\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // serial\n   //\n   div.appendChild(document.createTextNode('serial device:'))\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         serial_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         serial_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   //\n   // baud rate\n   //\n   div.appendChild(document.createTextNode('baud rate:'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.baud = input\n   div.appendChild(document.createElement('br'))   \n   //\n   // flow control\n   //\n   div.appendChild(document.createTextNode('flow control:'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'flow'\n      input.id = mod.div.id+'flow_none'\n      div.appendChild(input)\n      mod.flow_none = input\n   div.appendChild(document.createTextNode('none\\u00A0\\u00A0\\u00A0\\u00A0'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'flow'\n      input.id = mod.div.id+'flow_rtscts'\n      div.appendChild(input)\n      mod.flow_rtscts = input\n   div.appendChild(document.createTextNode('RTSCTS'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'flow'\n      input.id = mod.div.id+'flow_dsrdtr'\n      div.appendChild(input)\n      input.disabled = true\n      mod.flow_dsrdtr = input\n   div.appendChild(document.createTextNode('DSRDTR'))\n   div.appendChild(document.createElement('br'))   \n   //\n   // file button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file') {\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            mod.command = {}\n            mod.command.type = 'cancel'\n            socket_send(JSON.stringify(mod.command))\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      serial_open()\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      outputs.receive.event(event.data)\n      if ((event.data == 'done') || (event.data == 'cancel') || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\nfunction serial_open() {\n   if (mod.socket == null) {\n      mod.status.value = \"socket not open\"\n      }\n   else {\n      var msg = {}\n      msg.type = 'open'\n      msg.device = '/dev/'+mod.device.value\n      msg.baud = mod.baud.value\n      if (mod.flow_none.checked)\n         msg.flow = 'none'\n      else if (mod.flow_rtscts.checked)\n         msg.flow = 'rtscts'\n      else if (mod.flow_dsrdtr.checked)\n         msg.flow = 'dsrdtr'\n      mod.socket.send(JSON.stringify(msg))\n      }\n   }\nfunction serial_close() {\n   if (mod.socket == null) {\n      mod.status.value = \"socket not open\"\n      }\n   else {\n      var msg = {}\n      msg.type = 'close'\n      msg.device = '/dev/'+mod.device.value\n      mod.socket.send(JSON.stringify(msg))\n      }\n   }\nfunction serial_send_string(str) {\n   if (mod.socket == null) {\n      mod.status.value = \"socket not open\"\n      }\n   else {\n      var msg = {}\n      msg.type = 'string'\n      msg.string = str\n      mod.socket.send(JSON.stringify(msg))\n      mod.status.value = 'transmit'\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"167","left":"2021","inputs":{},"outputs":{}},"0.30171548718226804":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '2'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"407","left":"719","inputs":{},"outputs":{}},"0.734583870574828":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"142.4208530125389","left":"129.402514461341","inputs":{},"outputs":{}},"0.6332702301420967":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '500'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"698.3246286470918","left":"303.44828608790266","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7834289556361769\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.30171548718226804\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.734583870574828\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6332702301420967\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6332702301420967\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6332702301420967\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1212","left":"972","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1002","left":"1909","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"885","left":"2332","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1099","left":"1493","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1305","left":"508","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"865","left":"1175","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"113","left":"1184","filename":"undefined","inputs":{},"outputs":{}},"0.6542976287965465":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"755","left":"2785","filename":"undefined","inputs":{},"outputs":{}},"0.8677372177580144":{"definition":"//\n// Roland MDX-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland MDX-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 40.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 0\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin x (mm)\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      //div.appendChild(input)\n      mod.oz = input\n   //div.appendChild(document.createTextNode(' (mm)'))\n   //div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = mod.units*parseFloat(mod.jz.value);\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move home and stop')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\";\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"322","left":"1581","filename":"undefined","inputs":{},"outputs":{}},"0.7834289556361769":{"definition":"//\n// serial server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket serial'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'ttyUSB0'\n   mod.baud.value = 9600\n   mod.flow_rtscts.checked = true\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   transmit:{type:'string',\n      event:function(evt){\n         serial_send_string(evt.detail)\n         }\n      },\n   file:{type:'object',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.job.type = 'file'\n            mod.label.nodeValue = 'send file'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   receive:{type:'character',\n      event:function(str){\n         mods.output(mod,'receive',str)}}}\n//\n\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/serialserver.js'\n      a.innerHTML = 'serialserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // address\n   //\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   //\n   // port\n   //\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   //\n   // status\n   //\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // serial\n   //\n   div.appendChild(document.createTextNode('serial device:'))\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         serial_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         serial_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   //\n   // baud rate\n   //\n   div.appendChild(document.createTextNode('baud rate:'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.baud = input\n   div.appendChild(document.createElement('br'))   \n   //\n   // flow control\n   //\n   div.appendChild(document.createTextNode('flow control:'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'flow'\n      input.id = mod.div.id+'flow_none'\n      div.appendChild(input)\n      mod.flow_none = input\n   div.appendChild(document.createTextNode('none\\u00A0\\u00A0\\u00A0\\u00A0'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'flow'\n      input.id = mod.div.id+'flow_rtscts'\n      div.appendChild(input)\n      mod.flow_rtscts = input\n   div.appendChild(document.createTextNode('RTSCTS'))\n   div.appendChild(document.createElement('br'))   \n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'flow'\n      input.id = mod.div.id+'flow_dsrdtr'\n      div.appendChild(input)\n      input.disabled = true\n      mod.flow_dsrdtr = input\n   div.appendChild(document.createTextNode('DSRDTR'))\n   div.appendChild(document.createElement('br'))   \n   //\n   // file button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file') {\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            mod.command = {}\n            mod.command.type = 'cancel'\n            socket_send(JSON.stringify(mod.command))\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      serial_open()\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      outputs.receive.event(event.data)\n      if ((event.data == 'done') || (event.data == 'cancel') || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\nfunction serial_open() {\n   if (mod.socket == null) {\n      mod.status.value = \"socket not open\"\n      }\n   else {\n      var msg = {}\n      msg.type = 'open'\n      msg.device = '/dev/'+mod.device.value\n      msg.baud = mod.baud.value\n      if (mod.flow_none.checked)\n         msg.flow = 'none'\n      else if (mod.flow_rtscts.checked)\n         msg.flow = 'rtscts'\n      else if (mod.flow_dsrdtr.checked)\n         msg.flow = 'dsrdtr'\n      mod.socket.send(JSON.stringify(msg))\n      }\n   }\nfunction serial_close() {\n   if (mod.socket == null) {\n      mod.status.value = \"socket not open\"\n      }\n   else {\n      var msg = {}\n      msg.type = 'close'\n      msg.device = '/dev/'+mod.device.value\n      mod.socket.send(JSON.stringify(msg))\n      }\n   }\nfunction serial_send_string(str) {\n   if (mod.socket == null) {\n      mod.status.value = \"socket not open\"\n      }\n   else {\n      var msg = {}\n      msg.type = 'string'\n      msg.string = str\n      mod.socket.send(JSON.stringify(msg))\n      mod.status.value = 'transmit'\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"117","left":"2021","filename":"undefined","inputs":{},"outputs":{}},"0.734583870574828":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"92.42085301253891","left":"129.402514461341","filename":"undefined","inputs":{},"outputs":{}},"0.6332702301420967":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '500'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"648.3246286470918","left":"303.44828608790266","filename":"undefined","inputs":{},"outputs":{}},"0.30171548718226804":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"89","left":"570","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6542976287965465\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8677372177580144\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7834289556361769\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.734583870574828\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6332702301420967\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6332702301420967\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6332702301420967\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.30171548718226804\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/Roland/mill/SRM-20/PCB png b/programs/machines/Roland/mill/SRM-20/PCB png
index cd12adef94b1fc9fc23606d8e07efa450c77871b..6659b190593efa7797f02d89d156ae21b4b3fd38 100644
--- a/programs/machines/Roland/mill/SRM-20/PCB png	
+++ b/programs/machines/Roland/mill/SRM-20/PCB png	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"217","left":"2425","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"834","left":"2883","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"704","left":"2452","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"317","left":"2884","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"159","left":"270","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"350","left":"1913","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1054","left":"1243","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"275","left":"1266","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"820","left":"1957","inputs":{},"outputs":{}},"0.20431025191094654":{"definition":"//\n// devie server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket device'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'usb/lp0'\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            mod.command.device = mod.device.value\n            mod.command.type = 'print'\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.label.nodeValue = 'send file to device'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/deviceserver.js'\n      a.innerHTML = 'deviceserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('device:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file to device') {\n            mod.job.device = mod.device.value\n            mod.job.type = 'print'\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            socket_send('cancel')\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open socket\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      if ((event.data == 'done') || (event.data == 'cancel')\n         || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"698","left":"747","inputs":{},"outputs":{}},"0.3462137274004594":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n/*\nG-codes:\nG00X10.0\nG90 (absolute positioning)\nG21 (mm units)\n#.0 numbers\nG00 (positioning rapid move)\nG01 (linear motion)\nM03 (start spindle)\nM05 (stop spindle)\nF (feed rate mm/min, 6-1800)\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \n*/\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z30.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z50.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"752","left":"226","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"195","left":"722","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20431025191094654\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"167","left":"2425","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"784","left":"2883","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"654","left":"2452","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"267","left":"2884","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"109","left":"270","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"300","left":"1913","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1004","left":"1243","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"225","left":"1266","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"770","left":"1957","filename":"undefined","inputs":{},"outputs":{}},"0.20431025191094654":{"definition":"//\n// devie server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket device'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'usb/lp0'\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            mod.command.device = mod.device.value\n            mod.command.type = 'print'\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.label.nodeValue = 'send file to device'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/deviceserver.js'\n      a.innerHTML = 'deviceserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('device:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file to device') {\n            mod.job.device = mod.device.value\n            mod.job.type = 'print'\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            socket_send('cancel')\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open socket\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      if ((event.data == 'done') || (event.data == 'cancel')\n         || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"648","left":"747","filename":"undefined","inputs":{},"outputs":{}},"0.3462137274004594":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n/*\nG-codes:\nG00X10.0\nG90 (absolute positioning)\nG21 (mm units)\n#.0 numbers\nG00 (positioning rapid move)\nG01 (linear motion)\nM03 (start spindle)\nM05 (stop spindle)\nF (feed rate mm/min, 6-1800)\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \n*/\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z30.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z50.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"702","left":"226","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"82","left":"709","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20431025191094654\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/Roland/mill/SRM-20/PCB png conventional b/programs/machines/Roland/mill/SRM-20/PCB png conventional
index a306392183bff303261e8dc5093a69370f123404..e504d4b580a6b846a2e0d8004058143b4e08d12d 100644
--- a/programs/machines/Roland/mill/SRM-20/PCB png conventional	
+++ b/programs/machines/Roland/mill/SRM-20/PCB png conventional	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"217","left":"2425","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"834","left":"2883","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"704","left":"2452","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"317","left":"2884","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"159","left":"270","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"350","left":"1913","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1054","left":"1243","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      input.checked = true\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"275","left":"1266","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"820","left":"1957","inputs":{},"outputs":{}},"0.20431025191094654":{"definition":"//\n// devie server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket device'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'usb/lp0'\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            mod.command.device = mod.device.value\n            mod.command.type = 'print'\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.label.nodeValue = 'send file to device'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/deviceserver.js'\n      a.innerHTML = 'deviceserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('device:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file to device') {\n            mod.job.device = mod.device.value\n            mod.job.type = 'print'\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            socket_send('cancel')\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open socket\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      if ((event.data == 'done') || (event.data == 'cancel')\n         || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"698","left":"747","inputs":{},"outputs":{}},"0.3462137274004594":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n/*\nG-codes:\nG00X10.0\nG90 (absolute positioning)\nG21 (mm units)\n#.0 numbers\nG00 (positioning rapid move)\nG01 (linear motion)\nM03 (start spindle)\nM05 (stop spindle)\nF (feed rate mm/min, 6-1800)\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \n*/\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z30.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z50.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"752","left":"226","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"195","left":"722","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20431025191094654\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"167","left":"2425","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"784","left":"2883","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"654","left":"2452","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"267","left":"2884","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"109","left":"270","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"300","left":"1913","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1004","left":"1243","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      input.checked = true\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"225","left":"1266","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"770","left":"1957","filename":"undefined","inputs":{},"outputs":{}},"0.20431025191094654":{"definition":"//\n// devie server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket device'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'usb/lp0'\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            mod.command.device = mod.device.value\n            mod.command.type = 'print'\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.label.nodeValue = 'send file to device'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/deviceserver.js'\n      a.innerHTML = 'deviceserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('device:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file to device') {\n            mod.job.device = mod.device.value\n            mod.job.type = 'print'\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            socket_send('cancel')\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open socket\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      if ((event.data == 'done') || (event.data == 'cancel')\n         || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"648","left":"747","filename":"undefined","inputs":{},"outputs":{}},"0.3462137274004594":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n/*\nG-codes:\nG00X10.0\nG90 (absolute positioning)\nG21 (mm units)\n#.0 numbers\nG00 (positioning rapid move)\nG01 (linear motion)\nM03 (start spindle)\nM05 (stop spindle)\nF (feed rate mm/min, 6-1800)\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \n*/\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z30.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z50.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"702","left":"226","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"98","left":"715","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20431025191094654\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/Roland/mill/SRM-20/PCB svg b/programs/machines/Roland/mill/SRM-20/PCB svg
index 22151032178d36039cc14a0adc952f97b6e81fab..c2d718d339d46a85ef78848857a57e88e443bac6 100644
--- a/programs/machines/Roland/mill/SRM-20/PCB svg	
+++ b/programs/machines/Roland/mill/SRM-20/PCB svg	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"308.39493035729737","left":"2718.0223491074776","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"925.3949303572973","left":"3176.0223491074776","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"795.3949303572973","left":"2745.0223491074776","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"408.39493035729737","left":"3177.0223491074776","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"441.39493035729737","left":"2206.0223491074776","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1145.3949303572974","left":"1536.0223491074776","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"366.39493035729737","left":"1559.0223491074776","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"911.3949303572973","left":"2250.0223491074776","inputs":{},"outputs":{}},"0.20431025191094654":{"definition":"//\n// devie server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket device'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'usb/lp0'\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            mod.command.device = mod.device.value\n            mod.command.type = 'print'\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.label.nodeValue = 'send file to device'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/deviceserver.js'\n      a.innerHTML = 'deviceserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('device:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file to device') {\n            mod.job.device = mod.device.value\n            mod.job.type = 'print'\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            socket_send('cancel')\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open socket\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      if ((event.data == 'done') || (event.data == 'cancel')\n         || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"789.3949303572973","left":"1040.0223491074776","inputs":{},"outputs":{}},"0.3462137274004594":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n/*\nG-codes:\nG00X10.0\nG90 (absolute positioning)\nG21 (mm units)\n#.0 numbers\nG00 (positioning rapid move)\nG01 (linear motion)\nM03 (start spindle)\nM05 (stop spindle)\nF (feed rate mm/min, 6-1800)\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \n*/\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z30.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z50.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"790.5110675507295","left":"521.1377036197403","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"286.39493035729737","left":"1015.0223491074776","inputs":{},"outputs":{}},"0.6178788252064344":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"79.52056277178156","left":"210.26934291467055","inputs":{},"outputs":{}},"0.9047733368890062":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '500'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"177.85556386692787","left":"587.2750572821196","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20431025191094654\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6178788252064344\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9047733368890062\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9047733368890062\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9047733368890062\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"258.39493035729737","left":"2718.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"875.3949303572973","left":"3176.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"745.3949303572973","left":"2745.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"358.39493035729737","left":"3177.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"391.39493035729737","left":"2206.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1095.3949303572974","left":"1536.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"316.39493035729737","left":"1559.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"861.3949303572973","left":"2250.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.20431025191094654":{"definition":"//\n// devie server module\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'WebSocket device'\n//\n// initialization\n//\nvar init = function() {\n   mod.address.value = '127.0.0.1'\n   mod.port.value = 1234\n   mod.device.value = 'usb/lp0'\n   mod.socket = null\n   socket_open()\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'',\n      event:function(evt){\n         if (evt.detail.type == 'command') {\n            mod.command = evt.detail\n            mod.command.device = mod.device.value\n            mod.command.type = 'print'\n            socket_send(JSON.stringify(mod.command))\n            }\n         else if (evt.detail.type == 'file') {\n            mod.job = evt.detail\n            mod.label.nodeValue = 'send file to device'\n            mod.labelspan.style.fontWeight = 'bold'\n            }\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // server\n   //\n   var a = document.createElement('a')\n      a.href = './js/deviceserver.js'\n      a.innerHTML = 'deviceserver:'\n      a.target = '_blank'\n   div.appendChild(a)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('address: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.address = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.port = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.status = input\n   div.appendChild(document.createElement('br'))\n   //\n   // open/close\n   //\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('open'))\n      btn.addEventListener('click',function() {\n         socket_open()\n         })\n      div.appendChild(btn)\n   var btn = document.createElement('button')\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('close'))\n      btn.addEventListener('click',function() {\n         socket_close()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // device\n   //\n   div.appendChild(document.createTextNode('device:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('/dev/'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.device = input\n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('waiting for file')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         if (mod.socket == null) {\n            mod.status.value = \"can't send, not open\"\n            }\n         else if (mod.label.nodeValue == 'send file to device') {\n            mod.job.device = mod.device.value\n            mod.job.type = 'print'\n            socket_send(JSON.stringify(mod.job))\n            mod.label.nodeValue = 'cancel'\n            }\n         else if (mod.label.nodeValue == 'cancel') {\n            socket_send('cancel')\n            }\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\nfunction socket_open() {\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\n   mod.socket = new WebSocket(url)\n   mod.socket.onopen = function(event) {\n      mod.status.value = \"socket opened\"\n      }\n   mod.socket.onerror = function(event) {\n      mod.status.value = \"can not open socket\"\n      mod.socket = null\n      }\n   mod.socket.onmessage = function(event) {\n      mod.status.value = event.data\n      if ((event.data == 'done') || (event.data == 'cancel')\n         || (event.data.slice(0,5) == 'error')) {\n         mod.label.nodeValue = 'waiting for file'\n         mod.labelspan.style.fontWeight = 'normal'\n         }\n      }\n   }\nfunction socket_close() {\n   mod.socket.close()\n   mod.status.value = \"socket closed\"\n   mod.socket = null\n   }\nfunction socket_send(msg) {\n   if (mod.socket != null) {\n      mod.status.value = \"send\"\n      mod.socket.send(msg)\n      }\n   else {\n      mod.status.value = \"can't send, not open\"\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"739.3949303572973","left":"1040.0223491074776","filename":"undefined","inputs":{},"outputs":{}},"0.3462137274004594":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n/*\nG-codes:\nG00X10.0\nG90 (absolute positioning)\nG21 (mm units)\n#.0 numbers\nG00 (positioning rapid move)\nG01 (linear motion)\nM03 (start spindle)\nM05 (stop spindle)\nF (feed rate mm/min, 6-1800)\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \n*/\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z30.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         //\n         // G-code version\n         //\n         /*\n         str = '%\\n' // data start\n         str += 'G90\\n' // absolute units\n         str += 'G21\\n' // mm units\n         str += 'G00Z50.0\\n' // move z\n         str += 'M02\\n' // end of program\n         */\n         //\n         // RML version\n         //\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\n         //\n         // send command\n         //\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"740.5110675507295","left":"521.1377036197403","filename":"undefined","inputs":{},"outputs":{}},"0.6178788252064344":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"29.520562771781556","left":"210.26934291467055","filename":"undefined","inputs":{},"outputs":{}},"0.9047733368890062":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '500'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"127.85556386692787","left":"587.2750572821196","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"168.39493035729737","left":"1027.0223491074776","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3462137274004594\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20431025191094654\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6178788252064344\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9047733368890062\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9047733368890062\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9047733368890062\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/Roland/mill/SRM-20/PCB svg connect b/programs/machines/Roland/mill/SRM-20/PCB svg connect
index 8dbb42cdd7bfba1911366cb3aad5f938cf66f640..2c8a3c576d1d904375f252bab892194839b521a9 100644
--- a/programs/machines/Roland/mill/SRM-20/PCB svg connect	
+++ b/programs/machines/Roland/mill/SRM-20/PCB svg connect	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1220","left":"920","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1010","left":"1857","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"893","left":"2303","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1107","left":"1441","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1313","left":"456","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"872","left":"1138","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"115","left":"1132","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"793","left":"2719","inputs":{},"outputs":{}},"0.9980172439474251":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\";\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"330","left":"1528","inputs":{},"outputs":{}},"0.5899953716607196":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = 300\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         draw_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// draw image\n//\nfunction draw_image() {\n   var dpi = parseFloat(mod.dpi.value)\n   var units = parseFloat(mod.unitstext.value)\n   var width = parseInt(dpi*mod.width/units)\n   var height = parseInt(dpi*mod.height/units)\n   mod.pixels.nodeValue = width+' x '+height+\" (pixels)\"\n   mod.inches.nodeValue = (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n   mod.mm.nodeValue = (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (width > height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-height/width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*height/width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-width/height)\n         var y0 = 0\n         var w = mod.canvas.height*width/height\n         var h = mod.canvas.height\n         }\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.clearRect(0,0,width,height)\n         ctx.drawImage(img,0,0,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(mod.img,x0,y0,w,h)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"531.9392735868998","left":"14.076612942324061","inputs":{},"outputs":{}},"0.967564939861018":{"definition":"//\r\n// PCB module extracts PCB profiles from SolidWorks products through Tools/FabLab Connect command\r\n//\r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2017\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'PCB connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n   mod.port = getParameterByName('swPort') || '80'\r\n   mod.socket = 0\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   SVG:{type:'string',\r\n      event:function(data){\r\n         mods.output(mod,'SVG',data)}}}\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   div.appendChild(document.createTextNode('server:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n   input.type = 'text'\r\n   input.size = 12\r\n   div.appendChild(input)\r\n   mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('open'))\r\n   btn.addEventListener('click',function() {\r\n       socket_open()\r\n   })\r\n   div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('close'))\r\n   btn.addEventListener('click',function() {\r\n       socket_close()\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('traces and outline: '))\r\n\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Get Top Traces'))\r\n      btn.addEventListener('click', function () {\r\n\t  extract_top_SVGs()\r\n\t })\r\n   div.appendChild(btn)\r\n\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Get Bottom Traces'))\r\n      btn.addEventListener('click', function () {\r\n\t  extract_bottom_SVGs()\r\n\t })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Get Outline'))\r\n      btn.addEventListener('click', function () {\r\n\t  extract_outline_SVGs()\r\n\t })\r\n      div.appendChild(btn)\r\n\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\")\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url)\r\n    if (!results) return null\r\n    if (!results[2]) return ''\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"))\r\n}\r\n\r\n\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address+':'+mod.port\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function (event) {\r\n       mod.status.value = \"opened\"\r\n       var connect = {}\r\n       connect.modCmd = 'connect'\r\n       connect.owner = getParameterByName('swOwner')\r\n       connect.id = getParameterByName('swID')\r\n       socket_send(JSON.stringify(connect))\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = \"receive\"\r\n      var swData = JSON.parse(event.data)\r\n      if (swData.swType === \"FaceSVG\" || swData.swType === \"PCBTraceSVG\") {\r\n          outputs.SVG.event(swData.data)\r\n      }\r\n   }\r\n   mod.socket.onclose = function (event) {\r\n       mod.status.value = \"connection closed\"\r\n   }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"closed\"\r\n   mod.socket = 0\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != 0) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction extract_top_SVGs() {\r\n   var modcmd = new Object\r\n   modcmd.modCmd = \"ExtractTopPCB\"\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\nfunction extract_bottom_SVGs() {\r\n   var modcmd = new Object\r\n   modcmd.modCmd = \"ExtractBottomPCB\"\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\nfunction extract_outline_SVGs() {\r\n   var modcmd = new Object\r\n   modcmd.modCmd = \"AutoExtractPCB\"\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\n\r\n\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"150.87854717379963","left":"200.9148678288087","inputs":{},"outputs":{}},"0.06350204937311554":{"definition":"//\r\n// print server module\r\n//\r\n// Neil Gershenfeld \r\n// (c) Massachusetts Institute of Technology 2017\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'WebSocket print'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address.value = '127.0.0.1'\r\n   mod.port.value = 1234\r\n   mod.printers.value = 'Printer not found'\r\n   mod.socket = null\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   file:{type:'',\r\n      event:function(evt){\r\n         if (evt.detail.type == 'file') {\r\n            mod.job = evt.detail\r\n            mod.label.nodeValue = 'send file to printer'\r\n            mod.labelspan.style.fontWeight = 'bold'\r\n            }\r\n         else if (evt.detail.type == 'command') {\r\n            mod.job = evt.detail\r\n            mod.job.printer = mod.printers.value\r\n            socket_send(JSON.stringify(mod.job))\r\n            }\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // server\r\n   //\r\n   var a = document.createElement('a')\r\n      a.href = './js/printserver.js'\r\n      a.innerHTML = 'printserver:'\r\n      a.target = '_blank'\r\n   div.appendChild(a)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.address = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.port = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         socket_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         socket_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // printer\r\n   //\r\n   div.appendChild(document.createTextNode('printer:'))\r\n       div.appendChild(document.createElement('br'))\r\n       var select = document.createElement('select')\r\n       select.setAttribute('style', 'width:150px');\r\n\t   var el = document.createElement('option')\r\n          el.textContent = 'Printer not found'\r\n          el.value = 'Printer not found'\r\n          select.appendChild(el)\r\n       div.appendChild(select)\r\n       mod.printers = select \r\n   div.appendChild(document.createElement('br'))   \r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('waiting for file')\r\n            mod.label = text\r\n            span.appendChild(text)\r\n         mod.labelspan = span\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         if (mod.socket == null) {\r\n            mod.status.value = \"can't send, not open\"\r\n            }\r\n         else if (mod.label.nodeValue == 'send file to printer') {\r\n            mod.job.printer = mod.printers.value\r\n            socket_send(JSON.stringify(mod.job))\r\n            mod.label.nodeValue = 'cancel'\r\n            }\r\n         else if (mod.label.nodeValue == 'cancel') {\r\n            socket_send('cancel')\r\n            }\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n      mod.status.value = \"socket opened\"\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open socket\"\r\n      mod.socket = null\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = event.data\r\n      if ((event.data == 'done') || (event.data == 'cancel')\r\n         || (event.data.slice(0,5) == 'error')) {\r\n         mod.label.nodeValue = 'waiting for file'\r\n         mod.labelspan.style.fontWeight = 'normal'\r\n         }\r\n       else if (event.data[0] === '{'){\r\n         var printerInfo = JSON.parse(event.data)\r\n         var printerList = printerInfo['printerList']\r\n         if (printerList) {\r\n            while (mod.printers.hasChildNodes()) {\r\n               mod.printers.removeChild(mod.printers.lastChild);\r\n               }\r\n\r\n         for(var i = 0; i < printerList.length; i++){\r\n            var printer = printerList[i];\r\n            var el = document.createElement('option')\r\n            el.textContent = printer\r\n            el.value = printer\r\n            mod.printers.appendChild(el)\r\n            }\r\n            var defaultPrinter = printerInfo['default']\r\n            if(defaultPrinter && defaultPrinter.length>0){\r\n               mod.printers.value = defaultPrinter\r\n               }\r\n            }\r\n         }\r\n      }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"socket closed\"\r\n   mod.socket = null\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != null) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"281","left":"2041","inputs":{},"outputs":{}},"0.6358622669350307":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"281.3102197612079","left":"586.8644445624304","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9980172439474251\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5899953716607196\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5899953716607196\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.967564939861018\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5899953716607196\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9980172439474251\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.06350204937311554\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6358622669350307\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1234","left":"969","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1024","left":"1906","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"907","left":"2352","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1121","left":"1490","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1327","left":"505","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"886","left":"1187","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"129","left":"1181","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"807","left":"2768","filename":"undefined","inputs":{},"outputs":{}},"0.9980172439474251":{"definition":"//\n// Roland SRM-20 milling machine\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'Roland SRM-20 milling machine'\n//\n// initialization\n//\nvar init = function() {\n   mod.units = 100.0\n   mod.speed.value = 4\n   mod.ox.value = 10\n   mod.oy.value = 10\n   mod.oz.value = 10\n   mod.jz.value = 2\n   mod.hx.value = 0\n   mod.hy.value = 152.4\n   mod.hz.value = 60.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(obj){\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // speed\n   //\n   div.appendChild(document.createTextNode('speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.speed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // origin\n   //\n   div.appendChild(document.createTextNode('origin:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.ox = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.oz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to origin')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var x0 = mod.units*parseFloat(mod.ox.value);\n         var y0 = mod.units*parseFloat(mod.oy.value);\n         var z0 = mod.units*parseFloat(mod.oz.value);\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // jog\n   //\n   div.appendChild(document.createTextNode('jog height:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // home\n   //\n   div.appendChild(document.createTextNode('home:'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hx = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hy = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.hz = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('move to home')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         var xhome = mod.units*parseFloat(mod.hx.value);\n         var yhome = mod.units*parseFloat(mod.hy.value);\n         var zhome = mod.units*parseFloat(mod.hz.value);\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\";\n         var obj = {}\n         obj.type = 'command'\n         obj.name = mod.name+'.rml'\n         obj.contents = str\n         outputs.file.event(obj)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var nx = mod.width\n   var speed = parseFloat(mod.speed.value)\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\n   var ijog = Math.floor(mod.units*jog)\n   var scale = mod.units*dx/(nx-1)\n   var x0 = parseFloat(mod.ox.value)\n   var y0 = parseFloat(mod.oy.value)\n   var z0 = parseFloat(mod.oz.value)\n   var xoffset = mod.units*x0\n   var yoffset = mod.units*y0\n   var zoffset = mod.units*z0\n   var str = \"PA;PA;\" // plot absolute\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\n   str += \"!MC1;\\n\" // turn motor on\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = xoffset+scale*mod.path[seg][0][0]\n      y = yoffset+scale*mod.path[seg][0][1]\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      //\n      // move down\n      //\n      z = zoffset+scale*mod.path[seg][0][2]\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = xoffset+scale*mod.path[seg][pt][0]\n         y = yoffset+scale*mod.path[seg][pt][1]\n         z = zoffset+scale*mod.path[seg][pt][2]\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\n         }\n      //\n      // move up\n      //\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\n      }\n   //\n   // turn off motor and move back\n   //\n   var xhome = mod.units*parseFloat(mod.hx.value)\n   var yhome = mod.units*parseFloat(mod.hy.value)\n   var zhome = mod.units*parseFloat(mod.hz.value)\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\n   //\n   // output string\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.name+'.rml'\n   obj.contents = str\n   outputs.file.event(obj)\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"344","left":"1577","filename":"undefined","inputs":{},"outputs":{}},"0.5899953716607196":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = 300\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         draw_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// draw image\n//\nfunction draw_image() {\n   var dpi = parseFloat(mod.dpi.value)\n   var units = parseFloat(mod.unitstext.value)\n   var width = parseInt(dpi*mod.width/units)\n   var height = parseInt(dpi*mod.height/units)\n   mod.pixels.nodeValue = width+' x '+height+\" (pixels)\"\n   mod.inches.nodeValue = (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n   mod.mm.nodeValue = (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (width > height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-height/width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*height/width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-width/height)\n         var y0 = 0\n         var w = mod.canvas.height*width/height\n         var h = mod.canvas.height\n         }\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.clearRect(0,0,width,height)\n         ctx.drawImage(img,0,0,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(mod.img,x0,y0,w,h)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"532.9392735868998","left":"161.07661294232406","filename":"undefined","inputs":{},"outputs":{}},"0.967564939861018":{"definition":"//\r\n// PCB module extracts PCB profiles from SolidWorks products through Tools/FabLab Connect command\r\n//\r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2017\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'PCB connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n   mod.port = getParameterByName('swPort') || '80'\r\n   mod.socket = 0\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   SVG:{type:'string',\r\n      event:function(data){\r\n         mods.output(mod,'SVG',data)}}}\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   div.appendChild(document.createTextNode('server:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n   input.type = 'text'\r\n   input.size = 12\r\n   div.appendChild(input)\r\n   mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('open'))\r\n   btn.addEventListener('click',function() {\r\n       socket_open()\r\n   })\r\n   div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('close'))\r\n   btn.addEventListener('click',function() {\r\n       socket_close()\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('traces and outline: '))\r\n\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Get Top Traces'))\r\n      btn.addEventListener('click', function () {\r\n\t  extract_top_SVGs()\r\n\t })\r\n   div.appendChild(btn)\r\n\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Get Bottom Traces'))\r\n      btn.addEventListener('click', function () {\r\n\t  extract_bottom_SVGs()\r\n\t })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Get Outline'))\r\n      btn.addEventListener('click', function () {\r\n\t  extract_outline_SVGs()\r\n\t })\r\n      div.appendChild(btn)\r\n\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\")\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url)\r\n    if (!results) return null\r\n    if (!results[2]) return ''\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"))\r\n}\r\n\r\n\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address+':'+mod.port\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function (event) {\r\n       mod.status.value = \"opened\"\r\n       var connect = {}\r\n       connect.modCmd = 'connect'\r\n       connect.owner = getParameterByName('swOwner')\r\n       connect.id = getParameterByName('swID')\r\n       socket_send(JSON.stringify(connect))\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = \"receive\"\r\n      var swData = JSON.parse(event.data)\r\n      if (swData.swType === \"FaceSVG\" || swData.swType === \"PCBTraceSVG\") {\r\n          outputs.SVG.event(swData.data)\r\n      }\r\n   }\r\n   mod.socket.onclose = function (event) {\r\n       mod.status.value = \"connection closed\"\r\n   }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"closed\"\r\n   mod.socket = 0\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != 0) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction extract_top_SVGs() {\r\n   var modcmd = new Object\r\n   modcmd.modCmd = \"ExtractTopPCB\"\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\nfunction extract_bottom_SVGs() {\r\n   var modcmd = new Object\r\n   modcmd.modCmd = \"ExtractBottomPCB\"\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\nfunction extract_outline_SVGs() {\r\n   var modcmd = new Object\r\n   modcmd.modCmd = \"AutoExtractPCB\"\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\n\r\n\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"159.87854717379963","left":"182.9148678288087","filename":"undefined","inputs":{},"outputs":{}},"0.06350204937311554":{"definition":"//\r\n// print server module\r\n//\r\n// Neil Gershenfeld \r\n// (c) Massachusetts Institute of Technology 2017\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'WebSocket print'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address.value = '127.0.0.1'\r\n   mod.port.value = 1234\r\n   mod.printers.value = 'Printer not found'\r\n   mod.socket = null\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   file:{type:'',\r\n      event:function(evt){\r\n         if (evt.detail.type == 'file') {\r\n            mod.job = evt.detail\r\n            mod.label.nodeValue = 'send file to printer'\r\n            mod.labelspan.style.fontWeight = 'bold'\r\n            }\r\n         else if (evt.detail.type == 'command') {\r\n            mod.job = evt.detail\r\n            mod.job.printer = mod.printers.value\r\n            socket_send(JSON.stringify(mod.job))\r\n            }\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // server\r\n   //\r\n   var a = document.createElement('a')\r\n      a.href = './js/printserver.js'\r\n      a.innerHTML = 'printserver:'\r\n      a.target = '_blank'\r\n   div.appendChild(a)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.address = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.port = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         socket_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         socket_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // printer\r\n   //\r\n   div.appendChild(document.createTextNode('printer:'))\r\n       div.appendChild(document.createElement('br'))\r\n       var select = document.createElement('select')\r\n       select.setAttribute('style', 'width:150px');\r\n\t   var el = document.createElement('option')\r\n          el.textContent = 'Printer not found'\r\n          el.value = 'Printer not found'\r\n          select.appendChild(el)\r\n       div.appendChild(select)\r\n       mod.printers = select \r\n   div.appendChild(document.createElement('br'))   \r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('waiting for file')\r\n            mod.label = text\r\n            span.appendChild(text)\r\n         mod.labelspan = span\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         if (mod.socket == null) {\r\n            mod.status.value = \"can't send, not open\"\r\n            }\r\n         else if (mod.label.nodeValue == 'send file to printer') {\r\n            mod.job.printer = mod.printers.value\r\n            socket_send(JSON.stringify(mod.job))\r\n            mod.label.nodeValue = 'cancel'\r\n            }\r\n         else if (mod.label.nodeValue == 'cancel') {\r\n            socket_send('cancel')\r\n            }\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n      mod.status.value = \"socket opened\"\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open socket\"\r\n      mod.socket = null\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = event.data\r\n      if ((event.data == 'done') || (event.data == 'cancel')\r\n         || (event.data.slice(0,5) == 'error')) {\r\n         mod.label.nodeValue = 'waiting for file'\r\n         mod.labelspan.style.fontWeight = 'normal'\r\n         }\r\n       else if (event.data[0] === '{'){\r\n         var printerInfo = JSON.parse(event.data)\r\n         var printerList = printerInfo['printerList']\r\n         if (printerList) {\r\n            while (mod.printers.hasChildNodes()) {\r\n               mod.printers.removeChild(mod.printers.lastChild);\r\n               }\r\n\r\n         for(var i = 0; i < printerList.length; i++){\r\n            var printer = printerList[i];\r\n            var el = document.createElement('option')\r\n            el.textContent = printer\r\n            el.value = printer\r\n            mod.printers.appendChild(el)\r\n            }\r\n            var defaultPrinter = printerInfo['default']\r\n            if(defaultPrinter && defaultPrinter.length>0){\r\n               mod.printers.value = defaultPrinter\r\n               }\r\n            }\r\n         }\r\n      }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"socket closed\"\r\n   mod.socket = null\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != null) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"295","left":"2090","filename":"undefined","inputs":{},"outputs":{}},"0.6358622669350307":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"295.3102197612079","left":"635.8644445624304","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9980172439474251\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5899953716607196\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5899953716607196\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.967564939861018\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5899953716607196\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9980172439474251\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.06350204937311554\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6358622669350307\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}
diff --git a/programs/machines/ShopBot/mill 2D png PCB b/programs/machines/ShopBot/mill 2D png PCB
index 4bf57fe0d2b5407ea9dce6977df3e3a5eb40345b..5b453c656f424ce9161437f6fb60fefe6ce84cb4 100644
--- a/programs/machines/ShopBot/mill 2D png PCB	
+++ b/programs/machines/ShopBot/mill 2D png PCB	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"166.15086639947708","left":"2368.4814613563613","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"783.1508663994771","left":"2826.4814613563613","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"653.1508663994771","left":"2395.4814613563613","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"266.1508663994771","left":"2827.4814613563613","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"108.15086639947707","left":"213.48146135636145","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"299.1508663994771","left":"1856.4814613563613","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1003.1508663994771","left":"1186.4814613563613","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"224.15086639947708","left":"1209.4814613563613","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"769.1508663994771","left":"1900.4814613563613","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.024'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"144.15086639947708","left":"665.4814613563615","inputs":{},"outputs":{}},"0.3005754590702302":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '4'\n   mod.plungespeed.value = '4'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '10000'\n   mod.unitsin.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"750.0149087791722","left":"223.62316064032572","inputs":{},"outputs":{}},"0.556928016342012":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"751.2811082182459","left":"699.7141497320174","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3005754590702302\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3005754590702302\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.556928016342012\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"185.15086639947708","left":"2417.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"802.1508663994771","left":"2875.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"672.1508663994771","left":"2444.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"285.1508663994771","left":"2876.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"127.15086639947707","left":"262.4814613563615","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"318.1508663994771","left":"1905.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1022.1508663994771","left":"1235.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"243.15086639947708","left":"1258.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"788.1508663994771","left":"1949.4814613563613","filename":"undefined","inputs":{},"outputs":{}},"0.3005754590702302":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '4'\n   mod.plungespeed.value = '4'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '10000'\n   mod.unitsin.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"769.0149087791722","left":"272.62316064032575","filename":"undefined","inputs":{},"outputs":{}},"0.556928016342012":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"770.2811082182459","left":"748.7141497320174","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   add_output('copper clearing (1/32)')\n   add_variable('tool diameter (in)','var20')\n   mod.var20.value = '0.0312'\n   add_variable('cut depth (in)','var21')\n   mod.var21.value = '0.004'\n   add_variable('max depth (in)','var22')\n   mod.var22.value = '0.004'\n   add_variable('offset number','var23')\n   mod.var23.value = '0'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"163.15086639947708","left":"714.4814613563615","filename":"undefined","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3005754590702302\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3005754590702302\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.556928016342012\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}"]}