From c8ea4dcf5fa67fabcc81d68526fe557246dca28b Mon Sep 17 00:00:00 2001
From: Neil Gershenfeld <gersh@cba.mit.edu>
Date: Sat, 25 Jan 2020 22:30:32 +0530
Subject: [PATCH] ready for stepover

---
 modules/mesh/height map               |  1 +
 modules/processes/mill/raster/3D      | 10 +++++-----
 programs/machines/ShopBot/mill 3D stl |  2 +-
 3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/modules/mesh/height map b/modules/mesh/height map
index d42eff4..09e8562 100644
--- a/modules/mesh/height map	
+++ b/modules/mesh/height map	
@@ -53,6 +53,7 @@ var outputs = {
          obj.zmax = mod.zmax
          obj.width = mod.img.width
          obj.height = mod.img.height
+         obj.mmunits = mod.mmunits.value
          mods.output(mod,'map',obj)
          }}}
 //
diff --git a/modules/processes/mill/raster/3D b/modules/processes/mill/raster/3D
index 7c53026..36bd5fb 100644
--- a/modules/processes/mill/raster/3D
+++ b/modules/processes/mill/raster/3D
@@ -36,16 +36,17 @@ var inputs = {
    map:{type:'',label:'height map',
       event:function(evt){
          mod.map = evt.detail.map
-         mod.width = evt.detail.width
-         mod.height = evt.detail.height
-         mod.depth = Math.floor((mod.zmax-mod.zmin)*mod.width/(mod.xmax-mod.xmin))
          mod.xmin = evt.detail.xmin
          mod.xmax = evt.detail.xmax
          mod.ymin = evt.detail.ymin
          mod.ymax = evt.detail.ymax
          mod.zmin = evt.detail.zmin
          mod.zmax = evt.detail.zmax
-         mod.dpi = mod.width/(mod.xmax-mod.xmin)
+         mod.width = evt.detail.width
+         mod.height = evt.detail.height
+         mod.depth = Math.floor((mod.zmax-mod.zmin)*mod.width/(mod.xmax-mod.xmin))
+         mod.mmunits = evt.detail.mmunits
+         mod.dpi = mod.width/(mod.mmunits*(mod.xmax-mod.xmin)/25.4)
          var ctx = mod.img.getContext("2d")
          ctx.canvas.width = mod.width
          ctx.canvas.height = mod.height
@@ -64,7 +65,6 @@ var outputs = {
          obj.height = mod.height
          obj.depth = mod.depth
          mods.output(mod,'toolpath',obj)
-         console.log(mod.path[0].length)
          }}}
 //
 // interface
diff --git a/programs/machines/ShopBot/mill 3D stl b/programs/machines/ShopBot/mill 3D stl
index f0b8a4e..376c5ab 100644
--- a/programs/machines/ShopBot/mill 3D stl	
+++ b/programs/machines/ShopBot/mill 3D stl	
@@ -1 +1 @@
-{"modules":{"0.9903638182304415":{"definition":"//\n// read stl\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 = 'read STL'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}\n      }\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         stl_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // 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   // 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 stl 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   // info\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('name: ')\n         info.appendChild(text)\n         mod.namen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('size: ')\n         info.appendChild(text)\n         mod.sizen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('triangles: ')\n         info.appendChild(text)\n         mod.trianglesn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dx: ')\n         info.appendChild(text)\n         mod.dxn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dy: ')\n         info.appendChild(text)\n         mod.dyn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dz: ')\n         info.appendChild(text)\n         mod.dzn = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction stl_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = stl_load_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.namen.nodeValue = 'name: '+file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// load handler\n//\nfunction stl_load_handler(event) {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(event.target.result)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   if (size != view.byteLength) {\n      mod.sizen.nodeValue = 'error: not binary STL'\n      mod.trianglesn.nodeValue = ''\n      mod.dxn.nodeValue = ''\n      mod.dyn.nodeValue = ''\n      mod.dzn.nodeValue = ''\n      return\n      }\n   mod.sizen.nodeValue = 'size: '+size\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\n   //\n   // find limits and draw\n   //\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // worker response\n      //\n      window.URL.revokeObjectURL(url)\n      //\n      // size\n      //\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\n      //\n      // image\n      //\n      var image = evt.data.image\n      var height = mod.canvas.height\n      var width = mod.canvas.width\n      var buffer = new Uint8ClampedArray(evt.data.image)\n      var imgdata = new ImageData(buffer,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.mesh)\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var img = ctx.getImageData(0,0,mod.canvas.width,mod.canvas.height)\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      image:img.data.buffer,mesh:event.target.result},\n      [img.data.buffer,event.target.result])\n   }\nfunction draw_limits_worker() {\n   self.addEventListener('message',function(evt) {\n      //\n      // function to draw line\n      //\n      function line(x0,y0,x1,y1) {\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\n         var row,col\n         var idx = ix1-ix0\n         var idy = iy1-iy0\n         if (Math.abs(idy) > Math.abs(idx)) {\n            (idy > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (row = row0; row <= row1; ++row) {\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\n            (idx > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (col = col0; col <= col1; ++col) {\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else {\n            row = iy0\n            col = ix0\n            image[row*width*4+col*4+0] = 0\n            image[row*width*4+col*4+1] = 0\n            image[row*width*4+col*4+2] = 0\n            image[row*width*4+col*4+3] = 255\n            }\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var endian = true\n      var image = new Uint8ClampedArray(evt.data.image)\n      var view = new DataView(evt.data.mesh)\n      var triangles = view.getUint32(80,endian)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         offset += 2\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // draw mesh\n      //\n      if (dx > dy) {\n         var xo = 0\n         var yo = height*.5*(1-dy/dx)\n         var xw = width-1\n         var yh = (width-1)*dy/dx\n         }\n      else {\n         var xo = width*.5*(1-dx/dy)\n         var yo = 0\n         var xw = (height-1)*dx/dy\n         var yh = height-1\n         }\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         line(x0,y0,x1,y1)\n         line(x1,y1,x2,y2)\n         line(x2,y2,x0,y0)\n         }\n      //\n      // return results and close\n      //\n      self.postMessage({\n         dx:dx,dy:dy,dz:dz,\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\n      self.close()\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":"101.09025323814005","left":"124.55170608551913","inputs":{},"outputs":{}},"0.7269098240824425":{"definition":"//\n// mesh height map\n// \n// Neil Gershenfeld 1/16/20\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 = 'mesh height map'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '25.4'\n   mod.inunits.value = '1'\n   mod.width.value = '500'\n   mod.border.value = '0'\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = new DataView(evt.detail)\n         find_limits_map()}}}\n//\n// outputs\n//\nvar outputs = {\n   map:{type:'',label:'height map',\n      event:function(heightmap){\n         var obj = {}\n         obj.map = heightmap\n         obj.xmin = mod.xmin\n         obj.xmax = mod.xmax\n         obj.ymin = mod.ymin\n         obj.ymax = mod.ymax\n         obj.zmin = mod.zmin\n         obj.zmax = mod.zmax\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'map',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen height map canvas\n   //\n   div.appendChild(document.createTextNode(' '))\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.mapcanvas = 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   // mesh units\n   //\n   div.appendChild(document.createTextNode('mesh units: (enter)'))\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('change',function(){\n         mod.inunits.value = parseFloat(mod.mmunits.value)/25.4\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.mmunits = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         mod.mmunits.value = parseFloat(mod.inunits.value)*25.4\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.inunits = input\n   //\n   // mesh size\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mesh size:'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (units)')\n      div.appendChild(text)\n      mod.meshsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (mm)')\n      div.appendChild(text)\n      mod.mmsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (in)')\n      div.appendChild(text)\n      mod.insize = text\n   //\n   // height map border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // height map width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view height map\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 height map'))\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// find limits then map \n//\nfunction find_limits_map() {\n   var blob = new Blob(['('+limits_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      mod.triangles = evt.data.triangles\n      mod.xmin = evt.data.xmin\n      mod.xmax = evt.data.xmax\n      mod.ymin = evt.data.ymin\n      mod.ymax = evt.data.ymax\n      mod.zmin = evt.data.zmin\n      mod.zmax = evt.data.zmax\n      mod.dx = mod.xmax-mod.xmin\n      mod.dy = mod.ymax-mod.ymin\n      mod.dz = mod.zmax-mod.zmin\n      mod.meshsize.nodeValue = \n         mod.dx.toFixed(3)+' x '+\n         mod.dy.toFixed(3)+' x '+\n         mod.dz.toFixed(3)+' (units)'\n      var mm = parseFloat(mod.mmunits.value)\n      mod.mmsize.nodeValue = \n         (mod.dx*mm).toFixed(3)+' x '+\n         (mod.dy*mm).toFixed(3)+' x '+\n         (mod.dz*mm).toFixed(3)+' (mm)'\n      var inches = parseFloat(mod.inunits.value)\n      mod.insize.nodeValue = \n         (mod.dx*inches).toFixed(3)+' x '+\n         (mod.dy*inches).toFixed(3)+' x '+\n         (mod.dz*inches).toFixed(3)+' (in)'\n      mods.fit(mod.div)\n      map_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var border = evt.data.border\n      //\n      // get vars\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         }\n      xmin -= border\n      xmax += border\n      ymin -= border\n      ymax += border\n      //\n      // return\n      //\n      self.postMessage({triangles:triangles,\n         xmin:xmin,xmax:xmax,ymin:ymin,ymax:ymax,\n         zmin:zmin,zmax:zmax})\n      self.close()\n      })\n   }\n//\n// map mesh\n//   \nfunction map_mesh() {\n   var blob = new Blob(['('+map_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.imgbuffer)\n      var map = new Float32Array(evt.data.mapbuffer)\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.mapcanvas.height*.5*(1-h/w)\n         var wd = mod.mapcanvas.width\n         var hd = mod.mapcanvas.width*h/w\n         }\n      else {\n         var x0 = mod.mapcanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.mapcanvas.height*w/h\n         var hd = mod.mapcanvas.height\n         }\n      var ctx = mod.mapcanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.mapcanvas.width,mod.mapcanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.map.event(map)\n      })\n   var ctx = mod.mapcanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.mapcanvas.width,mod.mapcanvas.height)\n   mod.img.width = parseInt(mod.width.value)\n   mod.img.height = Math.round(mod.img.width*mod.dy/mod.dx)\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   var map = new Float32Array(mod.img.width*mod.img.height)\n   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,\n      imgbuffer:img.data.buffer,\n      mapbuffer:map.buffer,\n      mesh:mod.mesh,\n      xmin:mod.xmin,xmax:mod.xmax,\n      ymin:mod.ymin,ymax:mod.ymax,\n      zmin:mod.zmin,zmax:mod.zmax},\n      [img.data.buffer,map.buffer])\n   }\nfunction map_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var view = evt.data.mesh\n      var xmin = evt.data.xmin\n      var xmax = evt.data.xmax\n      var ymin = evt.data.ymin\n      var ymax = evt.data.ymax\n      var zmin = evt.data.zmin\n      var zmax = evt.data.zmax\n      var buf = new Uint8ClampedArray(evt.data.imgbuffer)\n      var map = new Float32Array(evt.data.mapbuffer)\n      //\n      // get vars from buffer\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // initialize map and image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            map[(h-1-row)*w+col] = zmin\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // loop over triangles\n      //\n      var segs = []\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         //\n         // check normal if needs to be drawn\n         //\n         if (((x1-x0)*(y1-y2)-(x1-x2)*(y1-y0)) >= 0)\n            continue\n         //\n         // quantize image coordinates\n         //\n         x0 = Math.floor((w-1)*(x0-xmin)/(xmax-xmin))\n         x1 = Math.floor((w-1)*(x1-xmin)/(xmax-xmin))\n         x2 = Math.floor((w-1)*(x2-xmin)/(xmax-xmin))\n         y0 = Math.floor((h-1)*(y0-ymin)/(ymax-ymin))\n         y1 = Math.floor((h-1)*(y1-ymin)/(ymax-ymin))\n         y2 = Math.floor((h-1)*(y2-ymin)/(ymax-ymin))\n         //\n         // sort projection order\n         //\n         if (y1 > y2) {\n            var temp = x1;\n            x1 = x2;\n            x2 = temp\n            var temp = y1;\n            y1 = y2;\n            y2 = temp\n            var temp = z1;\n            z1 = z2;\n            z2 = temp\n            }\n         if (y0 > y1) {\n            var temp = x0;\n            x0 = x1;\n            x1 = temp\n            var temp = y0;\n            y0 = y1;\n            y1 = temp\n            var temp = z0;\n            z0 = z1;\n            z1 = temp\n            }\n         if (y1 > y2) {\n            var temp = x1;\n            x1 = x2;\n            x2 = temp\n            var temp = y1;\n            y1 = y2;\n            y2 = temp\n            var temp = z1;\n            z1 = z2;\n            z2 = temp\n            }\n         //\n         // check orientation after sort\n         //\n         if (x1 < (x0+((x2-x0)*(y1-y0))/(y2-y0)))\n            var dir = 1;\n         else\n            var dir = -1;\n         //\n         // set z values\n         //\n         if (y2 != y1) {\n            for (var y = y1; y <= y2; ++y) {\n               x12 = Math.floor(0.5+x1+(y-y1)*(x2-x1)/(y2-y1))\n               z12 = z1+(y-y1)*(z2-z1)/(y2-y1)\n               x02 = Math.floor(0.5+x0+(y-y0)*(x2-x0)/(y2-y0))\n               z02 = z0+(y-y0)*(z2-z0)/(y2-y0)\n               if (x12 != x02)\n                  var slope = (z02-z12)/(x02-x12)\n               else\n                  var slope = 0\n               var x = x12 - dir\n               while (x != x02) {\n                  x += dir\n                  var z = z12+slope*(x-x12)\n                  if (z > map[(h-1-y)*w+x]) {\n                     map[(h-1-y)*w+x] = z\n                     var iz = Math.floor(255*(z-zmin)/(zmax-zmin))\n                     buf[(h-1-y)*w*4+x*4+0] = iz\n                     buf[(h-1-y)*w*4+x*4+1] = iz\n                     buf[(h-1-y)*w*4+x*4+2] = iz\n                     }\n                  }\n               }\n            }\n         if (y1 != y0) {\n            for (var y = y0; y <= y1; ++y) {\n               x01 = Math.floor(0.5+x0+(y-y0)*(x1-x0)/(y1-y0))\n               z01 = z0+(y-y0)*(z1-z0)/(y1-y0)\n               x02 = Math.floor(0.5+x0+(y-y0)*(x2-x0)/(y2-y0))\n               z02 = z0+(y-y0)*(z2-z0)/(y2-y0)\n               if (x01 != x02)\n                  var slope = (z02-z01)/(x02-x01)\n               else\n                  var slope = 0\n               var x = x01 - dir\n               while (x != x02) {\n                  x += dir\n                  var z = z01+slope*(x-x01)\n                  if (z > map[(h-1-y)*w+x]) {\n                     map[(h-1-y)*w+x] = z\n                     var iz = Math.floor(255*(z-zmin)/(zmax-zmin))\n                     buf[(h-1-y)*w*4+x*4+0] = iz\n                     buf[(h-1-y)*w*4+x*4+1] = iz\n                     buf[(h-1-y)*w*4+x*4+2] = iz\n                     }\n                  }\n               }\n            }\n         }\n      //\n      // output the map\n      //\n      self.postMessage({imgbuffer:buf.buffer,mapbuffer:map.buffer},[buf.buffer,map.buffer])\n      self.close()\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":"226.8816957510627","left":"550.590521779755","inputs":{},"outputs":{}},"0.09986325972778665":{"definition":"//\n// mill raster 3D (incomplete)\n//\n// Neil Gershenfeld 1/18/20\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 3D (incomplete)'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.0156'\n   mod.dia_mm.value = '0.39624'\n   mod.stepover.value = '0.5'\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   map:{type:'',label:'height map',\n      event:function(evt){\n         mod.map = evt.detail.map\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.xmin = evt.detail.xmin\n         mod.xmax = evt.detail.xmax\n         mod.ymin = evt.detail.ymin\n         mod.ymax = evt.detail.ymax\n         mod.zmin = evt.detail.zmin\n         mod.zmax = evt.detail.zmax\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'',\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// 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   // stepover\n   //\n   div.appendChild(document.createTextNode('stepover (0-1): '))\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.createElement('br'))\n   //\n   // tool shape\n   //\n   div.appendChild(document.createTextNode('tool shape: '))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'shape'\n      input.id = mod.div.id+'flatend'\n      input.checked = true\n      div.appendChild(input)\n      mod.flatend= input\n   div.appendChild(document.createTextNode('flat end'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction \n   //\n   div.appendChild(document.createTextNode('direction: '))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'dirx'\n      input.checked = true\n      div.appendChild(input)\n      mod.dirx = input\n   div.appendChild(document.createTextNode('xz'))\n   div.appendChild(document.createElement('br'))\n   //\n   // fit error \n   //\n   div.appendChild(document.createTextNode('vector fit (%): '))\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   // 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         calculate_path()\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// calculate_path\n//\nfunction calculate_path() {\n   var h = mod.height\n   var w = mod.width\n   var xmin = mod.xmin\n   var xmax = mod.xmax\n   var ymin = mod.ymin\n   var ymax = mod.ymax\n   var zmin = mod.zmin\n   var zmax = mod.zmax\n   //\n   // clear SVG\n   //\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(w-1)+\" \"+(h-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   // line loop\n   //\n   var ix = 0\n   var iy = h-1\n   var dx = 1\n   var dy = 0\n   var x = xmin+(xmax-xmin)*ix/(w-1)\n   var y = ymin+(ymax-ymin)*iy/(h-1)\n   var z = mod.map[(h-1-iy)*w+ix]\n   var dz = 0.1*h*(zmax-z)/(ymax-ymin)\n   while (1) {\n      var ixp = ix\n      var iyp = iy\n      var dzp = dz\n      ix += dx\n      iy += dy\n      if (iy <= 0)\n         break;\n      var x = xmin+(xmax-xmin)*ix/(w-1)\n      var y = ymin+(ymax-ymin)*iy/(h-1)\n      var z = mod.map[iy*w+ix]\n      var dz = 0.1*h*(zmax-z)/(zmax-zmin)\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         line.setAttribute('x1',ixp)\n         line.setAttribute('y1',iyp+dzp)\n         line.setAttribute('x2',ix)\n         line.setAttribute('y2',iy+dz)\n         g.appendChild(line)\n      if (ix == (mod.width-1)) {\n         if (dx == 1) {\n            dx = 0\n            dy = -10\n            }\n         else {\n            dx = -1\n            dy = 0\n            }\n         }\n      else if (ix == 0) {\n         if (dx == -1) {\n            dx = 0\n            dy = -10\n            }\n         else {\n            dx = 1\n            dy = 0\n            }\n         }\n      }\n   mod.label.nodeValue = 'calculate'\n   mod.labelspan.style.fontWeight = 'normal'\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":"114.0870285990817","left":"987.3074815152157","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.9903638182304415\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7269098240824425\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7269098240824425\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"map\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.09986325972778665\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"map\\\"}\"}"]}
\ No newline at end of file
+{"modules":{"0.9903638182304415":{"definition":"//\n// read stl\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 = 'read STL'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}\n      }\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         stl_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // 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   // 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 stl 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   // info\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('name: ')\n         info.appendChild(text)\n         mod.namen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('size: ')\n         info.appendChild(text)\n         mod.sizen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('triangles: ')\n         info.appendChild(text)\n         mod.trianglesn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dx: ')\n         info.appendChild(text)\n         mod.dxn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dy: ')\n         info.appendChild(text)\n         mod.dyn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dz: ')\n         info.appendChild(text)\n         mod.dzn = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction stl_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = stl_load_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.namen.nodeValue = 'name: '+file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// load handler\n//\nfunction stl_load_handler(event) {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(event.target.result)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   if (size != view.byteLength) {\n      mod.sizen.nodeValue = 'error: not binary STL'\n      mod.trianglesn.nodeValue = ''\n      mod.dxn.nodeValue = ''\n      mod.dyn.nodeValue = ''\n      mod.dzn.nodeValue = ''\n      return\n      }\n   mod.sizen.nodeValue = 'size: '+size\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\n   //\n   // find limits and draw\n   //\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // worker response\n      //\n      window.URL.revokeObjectURL(url)\n      //\n      // size\n      //\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\n      //\n      // image\n      //\n      var image = evt.data.image\n      var height = mod.canvas.height\n      var width = mod.canvas.width\n      var buffer = new Uint8ClampedArray(evt.data.image)\n      var imgdata = new ImageData(buffer,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.mesh)\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var img = ctx.getImageData(0,0,mod.canvas.width,mod.canvas.height)\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      image:img.data.buffer,mesh:event.target.result},\n      [img.data.buffer,event.target.result])\n   }\nfunction draw_limits_worker() {\n   self.addEventListener('message',function(evt) {\n      //\n      // function to draw line\n      //\n      function line(x0,y0,x1,y1) {\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\n         var row,col\n         var idx = ix1-ix0\n         var idy = iy1-iy0\n         if (Math.abs(idy) > Math.abs(idx)) {\n            (idy > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (row = row0; row <= row1; ++row) {\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\n            (idx > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (col = col0; col <= col1; ++col) {\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else {\n            row = iy0\n            col = ix0\n            image[row*width*4+col*4+0] = 0\n            image[row*width*4+col*4+1] = 0\n            image[row*width*4+col*4+2] = 0\n            image[row*width*4+col*4+3] = 255\n            }\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var endian = true\n      var image = new Uint8ClampedArray(evt.data.image)\n      var view = new DataView(evt.data.mesh)\n      var triangles = view.getUint32(80,endian)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         offset += 2\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // draw mesh\n      //\n      if (dx > dy) {\n         var xo = 0\n         var yo = height*.5*(1-dy/dx)\n         var xw = width-1\n         var yh = (width-1)*dy/dx\n         }\n      else {\n         var xo = width*.5*(1-dx/dy)\n         var yo = 0\n         var xw = (height-1)*dx/dy\n         var yh = height-1\n         }\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         line(x0,y0,x1,y1)\n         line(x1,y1,x2,y2)\n         line(x2,y2,x0,y0)\n         }\n      //\n      // return results and close\n      //\n      self.postMessage({\n         dx:dx,dy:dy,dz:dz,\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\n      self.close()\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":"90.24915179990126","left":"102.0959522649269","inputs":{},"outputs":{}},"0.7269098240824425":{"definition":"//\n// mesh height map\n// \n// Neil Gershenfeld 1/16/20\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 = 'mesh height map'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '25.4'\n   mod.inunits.value = '1'\n   mod.width.value = '500'\n   mod.border.value = '0'\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = new DataView(evt.detail)\n         find_limits_map()}}}\n//\n// outputs\n//\nvar outputs = {\n   map:{type:'',label:'height map',\n      event:function(heightmap){\n         var obj = {}\n         obj.map = heightmap\n         obj.xmin = mod.xmin\n         obj.xmax = mod.xmax\n         obj.ymin = mod.ymin\n         obj.ymax = mod.ymax\n         obj.zmin = mod.zmin\n         obj.zmax = mod.zmax\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'map',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen height map canvas\n   //\n   div.appendChild(document.createTextNode(' '))\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.mapcanvas = 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   // mesh units\n   //\n   div.appendChild(document.createTextNode('mesh units: (enter)'))\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('change',function(){\n         mod.inunits.value = parseFloat(mod.mmunits.value)/25.4\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.mmunits = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         mod.mmunits.value = parseFloat(mod.inunits.value)*25.4\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.inunits = input\n   //\n   // mesh size\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mesh size:'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (units)')\n      div.appendChild(text)\n      mod.meshsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (mm)')\n      div.appendChild(text)\n      mod.mmsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (in)')\n      div.appendChild(text)\n      mod.insize = text\n   //\n   // height map border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // height map width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_map()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view height map\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 height map'))\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// find limits then map \n//\nfunction find_limits_map() {\n   var blob = new Blob(['('+limits_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      mod.triangles = evt.data.triangles\n      mod.xmin = evt.data.xmin\n      mod.xmax = evt.data.xmax\n      mod.ymin = evt.data.ymin\n      mod.ymax = evt.data.ymax\n      mod.zmin = evt.data.zmin\n      mod.zmax = evt.data.zmax\n      mod.dx = mod.xmax-mod.xmin\n      mod.dy = mod.ymax-mod.ymin\n      mod.dz = mod.zmax-mod.zmin\n      mod.meshsize.nodeValue = \n         mod.dx.toFixed(3)+' x '+\n         mod.dy.toFixed(3)+' x '+\n         mod.dz.toFixed(3)+' (units)'\n      var mm = parseFloat(mod.mmunits.value)\n      mod.mmsize.nodeValue = \n         (mod.dx*mm).toFixed(3)+' x '+\n         (mod.dy*mm).toFixed(3)+' x '+\n         (mod.dz*mm).toFixed(3)+' (mm)'\n      var inches = parseFloat(mod.inunits.value)\n      mod.insize.nodeValue = \n         (mod.dx*inches).toFixed(3)+' x '+\n         (mod.dy*inches).toFixed(3)+' x '+\n         (mod.dz*inches).toFixed(3)+' (in)'\n      mods.fit(mod.div)\n      map_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var border = evt.data.border\n      //\n      // get vars\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         }\n      xmin -= border\n      xmax += border\n      ymin -= border\n      ymax += border\n      //\n      // return\n      //\n      self.postMessage({triangles:triangles,\n         xmin:xmin,xmax:xmax,ymin:ymin,ymax:ymax,\n         zmin:zmin,zmax:zmax})\n      self.close()\n      })\n   }\n//\n// map mesh\n//   \nfunction map_mesh() {\n   var blob = new Blob(['('+map_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.imgbuffer)\n      var map = new Float32Array(evt.data.mapbuffer)\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.mapcanvas.height*.5*(1-h/w)\n         var wd = mod.mapcanvas.width\n         var hd = mod.mapcanvas.width*h/w\n         }\n      else {\n         var x0 = mod.mapcanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.mapcanvas.height*w/h\n         var hd = mod.mapcanvas.height\n         }\n      var ctx = mod.mapcanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.mapcanvas.width,mod.mapcanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.map.event(map)\n      })\n   var ctx = mod.mapcanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.mapcanvas.width,mod.mapcanvas.height)\n   mod.img.width = parseInt(mod.width.value)\n   mod.img.height = Math.round(mod.img.width*mod.dy/mod.dx)\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   var map = new Float32Array(mod.img.width*mod.img.height)\n   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,\n      imgbuffer:img.data.buffer,\n      mapbuffer:map.buffer,\n      mesh:mod.mesh,\n      xmin:mod.xmin,xmax:mod.xmax,\n      ymin:mod.ymin,ymax:mod.ymax,\n      zmin:mod.zmin,zmax:mod.zmax},\n      [img.data.buffer,map.buffer])\n   }\nfunction map_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var view = evt.data.mesh\n      var xmin = evt.data.xmin\n      var xmax = evt.data.xmax\n      var ymin = evt.data.ymin\n      var ymax = evt.data.ymax\n      var zmin = evt.data.zmin\n      var zmax = evt.data.zmax\n      var buf = new Uint8ClampedArray(evt.data.imgbuffer)\n      var map = new Float32Array(evt.data.mapbuffer)\n      //\n      // get vars from buffer\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // initialize map and image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            map[(h-1-row)*w+col] = zmin\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // loop over triangles\n      //\n      var segs = []\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         //\n         // check normal if needs to be drawn\n         //\n         if (((x1-x0)*(y1-y2)-(x1-x2)*(y1-y0)) >= 0)\n            continue\n         //\n         // quantize image coordinates\n         //\n         x0 = Math.floor((w-1)*(x0-xmin)/(xmax-xmin))\n         x1 = Math.floor((w-1)*(x1-xmin)/(xmax-xmin))\n         x2 = Math.floor((w-1)*(x2-xmin)/(xmax-xmin))\n         y0 = Math.floor((h-1)*(y0-ymin)/(ymax-ymin))\n         y1 = Math.floor((h-1)*(y1-ymin)/(ymax-ymin))\n         y2 = Math.floor((h-1)*(y2-ymin)/(ymax-ymin))\n         //\n         // sort projection order\n         //\n         if (y1 > y2) {\n            var temp = x1;\n            x1 = x2;\n            x2 = temp\n            var temp = y1;\n            y1 = y2;\n            y2 = temp\n            var temp = z1;\n            z1 = z2;\n            z2 = temp\n            }\n         if (y0 > y1) {\n            var temp = x0;\n            x0 = x1;\n            x1 = temp\n            var temp = y0;\n            y0 = y1;\n            y1 = temp\n            var temp = z0;\n            z0 = z1;\n            z1 = temp\n            }\n         if (y1 > y2) {\n            var temp = x1;\n            x1 = x2;\n            x2 = temp\n            var temp = y1;\n            y1 = y2;\n            y2 = temp\n            var temp = z1;\n            z1 = z2;\n            z2 = temp\n            }\n         //\n         // check orientation after sort\n         //\n         if (x1 < (x0+((x2-x0)*(y1-y0))/(y2-y0)))\n            var dir = 1;\n         else\n            var dir = -1;\n         //\n         // set z values\n         //\n         if (y2 != y1) {\n            for (var y = y1; y <= y2; ++y) {\n               x12 = Math.floor(0.5+x1+(y-y1)*(x2-x1)/(y2-y1))\n               z12 = z1+(y-y1)*(z2-z1)/(y2-y1)\n               x02 = Math.floor(0.5+x0+(y-y0)*(x2-x0)/(y2-y0))\n               z02 = z0+(y-y0)*(z2-z0)/(y2-y0)\n               if (x12 != x02)\n                  var slope = (z02-z12)/(x02-x12)\n               else\n                  var slope = 0\n               var x = x12 - dir\n               while (x != x02) {\n                  x += dir\n                  var z = z12+slope*(x-x12)\n                  if (z > map[(h-1-y)*w+x]) {\n                     map[(h-1-y)*w+x] = z\n                     var iz = Math.floor(255*(z-zmin)/(zmax-zmin))\n                     buf[(h-1-y)*w*4+x*4+0] = iz\n                     buf[(h-1-y)*w*4+x*4+1] = iz\n                     buf[(h-1-y)*w*4+x*4+2] = iz\n                     }\n                  }\n               }\n            }\n         if (y1 != y0) {\n            for (var y = y0; y <= y1; ++y) {\n               x01 = Math.floor(0.5+x0+(y-y0)*(x1-x0)/(y1-y0))\n               z01 = z0+(y-y0)*(z1-z0)/(y1-y0)\n               x02 = Math.floor(0.5+x0+(y-y0)*(x2-x0)/(y2-y0))\n               z02 = z0+(y-y0)*(z2-z0)/(y2-y0)\n               if (x01 != x02)\n                  var slope = (z02-z01)/(x02-x01)\n               else\n                  var slope = 0\n               var x = x01 - dir\n               while (x != x02) {\n                  x += dir\n                  var z = z01+slope*(x-x01)\n                  if (z > map[(h-1-y)*w+x]) {\n                     map[(h-1-y)*w+x] = z\n                     var iz = Math.floor(255*(z-zmin)/(zmax-zmin))\n                     buf[(h-1-y)*w*4+x*4+0] = iz\n                     buf[(h-1-y)*w*4+x*4+1] = iz\n                     buf[(h-1-y)*w*4+x*4+2] = iz\n                     }\n                  }\n               }\n            }\n         }\n      //\n      // output the map\n      //\n      self.postMessage({imgbuffer:buf.buffer,mapbuffer:map.buffer},[buf.buffer,map.buffer])\n      self.close()\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":"216.04059431282388","left":"528.1347679591628","inputs":{},"outputs":{}},"0.36382263595289266":{"definition":"//\n// view path\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2019\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 path'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\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         mods.fit(mod.div)\n         outputs.path.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'',\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,'path',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      renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_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//\nfunction init_window() {\n   //document.write('<script type=\"text/javascript\">'+arg+'</script>')\n   //document.close()\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         mod.win.close()\n         mod.win = undefined\n         })\n      mod.win.document.body.appendChild(btn)\n   //\n   // label text\n   //\n   var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n      mod.win.document.body.appendChild(text)\n   //\n   // GL container\n   //\n   mod.win.document.body.appendChild(document.createElement('br'))   \n   container = mod.win.document.createElement('div')\n   container.style.overflow = 'hidden'\n   mod.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   scene = new THREE.Scene()\n   mod.scene = scene\n   var width = mod.win.innerWidth\n   var height = mod.win.innerHeight\n   var aspect = width/height\n   var near = 0.1\n   var far = 1000000\n   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n   mod.camera = camera\n   scene.add(camera)\n   //\n   // add renderer\n   //\n   renderer = new THREE.WebGLRenderer({antialias:true})\n   mod.renderer = renderer\n   renderer.setClearColor(0xffffff)\n   renderer.setSize(width,height)\n   container.appendChild(renderer.domElement)\n   //\n   // show the path if available\n   //\n   show_path()\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/mod.win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/mod.win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/mod.win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/mod.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         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/mod.win.innerHeight\n         mod.thetaz += dx/mod.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         if (Math.cos(mod.thetaxy) > 0)\n            camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         else\n            camera.up = new THREE.Vector3(-Math.sin(mod.thetaz),-Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/mod.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      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n      renderer.render(scene,camera)\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":"260.3760318972536","left":"1427.9604217500023","inputs":{},"outputs":{}},"0.11764362271570472":{"definition":"//\n// mill raster 3D (incomplete)\n//\n// Neil Gershenfeld 1/18/20\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 3D (incomplete)'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.0156'\n   mod.dia_mm.value = '0.39624'\n   mod.stepover.value = '0.5'\n   mod.error.value = '0.001'\n   }\n//\n// inputs\n//\nvar inputs = {\n   map:{type:'',label:'height map',\n      event:function(evt){\n         mod.map = evt.detail.map\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = Math.floor((mod.zmax-mod.zmin)*mod.width/(mod.xmax-mod.xmin))\n         mod.xmin = evt.detail.xmin\n         mod.xmax = evt.detail.xmax\n         mod.ymin = evt.detail.ymin\n         mod.ymax = evt.detail.ymax\n         mod.zmin = evt.detail.zmin\n         mod.zmax = evt.detail.zmax\n         mod.dpi = mod.width/(mod.xmax-mod.xmin)\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'',\n      event:function(){\n         obj = {}\n         obj.path = mod.path\n         obj.name = \"mill raster 3D\"\n         obj.dpi = mod.dpi\n         obj.width = mod.width\n         obj.height = mod.height\n         obj.depth = mod.depth\n         mods.output(mod,'toolpath',obj)\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   // stepover\n   //\n   div.appendChild(document.createTextNode('stepover (0-1): '))\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.createElement('br'))\n   //\n   // tool shape\n   //\n   div.appendChild(document.createTextNode('tool shape: '))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'shape'\n      input.id = mod.div.id+'flatend'\n      input.checked = true\n      div.appendChild(input)\n      mod.flatend= input\n   div.appendChild(document.createTextNode('flat end'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction \n   //\n   div.appendChild(document.createTextNode('direction: '))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'dirx'\n      input.checked = true\n      div.appendChild(input)\n      mod.dirx = input\n   div.appendChild(document.createTextNode('xz'))\n   div.appendChild(document.createElement('br'))\n   //\n   // fit error \n   //\n   div.appendChild(document.createTextNode('vector fit: '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.error = 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         calculate_path()\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// calculate path\n//\nfunction calculate_path() {\n   var blob = new Blob(['('+calculate_path_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // webworker handler\n      //\n      mod.path = evt.data.path\n      mod.label.nodeValue = 'calculate'\n      mod.labelspan.style.fontWeight = 'normal'\n      //\n      // clear SVG\n      //\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.width-1)+\" \"+(mod.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      // plot path\n      //\n      for (var i = 1; i < mod.path[0].length; ++i) {\n         var ixp = mod.path[0][i-1][0]\n         var iyp = mod.path[0][i-1][1]\n         var izp = 0.1*mod.path[0][i-1][2]\n         var ix = mod.path[0][i][0]\n         var iy = mod.path[0][i][1]\n         var iz = 0.1*mod.path[0][i][2]\n         var line = document.createElementNS(\n               '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            line.setAttribute('x1',ixp)\n            line.setAttribute('y1',iyp-izp)\n            line.setAttribute('x2',ix)\n            line.setAttribute('y2',iy-iz)\n            g.appendChild(line)\n            }\n      //\n      // output path\n      //\n      outputs.toolpath.event()\n      })\n   //\n   // call webworker\n   //\n   webworker.postMessage({\n      h:mod.height,w:mod.width,error:mod.error.value,\n      xmin:mod.xmin,xmax:mod.xmax,\n      ymin:mod.ymin,ymax:mod.ymax,\n      zmin:mod.zmin,zmax:mod.zmax,\n      map:mod.map})\n   }\n//\n// calculate path worker\n//\nfunction calculate_path_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.h\n      var w = evt.data.w\n      var error = evt.data.error\n      var xmin = evt.data.xmin\n      var xmax = evt.data.xmax\n      var ymin = evt.data.ymin\n      var ymax = evt.data.ymax\n      var zmin = evt.data.zmin\n      var zmax = evt.data.zmax\n      var map = evt.data.map\n      var path = [[]]\n      //\n      // loop over lines\n      //\n      xstart = 0\n      ystart = h-1\n      zstart = Math.floor((map[ystart*w+xstart]-zmax)*w/(xmax-xmin))\n      path[0].push([xstart,ystart,zstart])\n      xcur = 1\n      ycur = h-1\n      zcur = Math.floor((map[ycur*w+xcur]-zmax)*w/(xmax-xmin))\n      dx = 1\n      dy = 0\n      while (1) {\n         //\n         // vectorize\n         //\n         xnext = xcur+dx\n         ynext = ycur+dy\n         znext = Math.floor((map[ynext*w+xnext]-zmax)*w/(xmax-xmin))\n         if (ynext <= 0)\n            break;\n         dxcur = xcur-xstart\n         dycur = ycur-ystart\n         dzcur = zcur-zstart\n         dcur = Math.sqrt(dxcur*dxcur+dycur*dycur+dzcur*dzcur)\n         nxcur = dxcur/dcur\n         nycur = dycur/dcur\n         nzcur = dzcur/dcur\n         dxnext = xnext-xcur\n         dynext = ynext-ycur\n         dznext = znext-zcur\n         dnext = Math.sqrt(dxnext*dxnext+dynext*dynext+dznext*dznext)\n         nxnext = dxnext/dnext\n         nynext = dynext/dnext\n         nznext = dznext/dnext\n         dot = nxcur*nxnext+nycur*nynext+nzcur*nznext\n         if (dot <= (1-error)) {\n            path[0].push([xcur,ycur,zcur])\n            xstart = xcur\n            ystart = ycur\n            zstart = zcur\n            }\n         xcur = xnext\n         ycur = ynext\n         zcur = znext\n         if (xcur == (w-1)) {\n            if (dx == 1) {\n               dx = 0\n               dy = -10\n               }\n            else {\n               dx = -1\n               dy = 0\n               }\n            }\n         else if (xcur == 0) {\n            if (dx == -1) {\n               dx = 0\n               dy = -10\n               }\n            else {\n               dx = 1\n               dy = 0\n               }\n            }\n         }\n      //\n      // return\n      //\n      self.postMessage({path:path})\n      self.close()\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\n","top":"117.98691577370033","left":"948.4463703512538","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.9903638182304415\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7269098240824425\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7269098240824425\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"map\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.11764362271570472\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"map\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.11764362271570472\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.36382263595289266\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}"]}
\ No newline at end of file
-- 
GitLab