From 1b30a949e1f098540508ca1df7487bf23bedfed8 Mon Sep 17 00:00:00 2001
From: Neil Gershenfeld <gersh@cba.mit.edu>
Date: Tue, 26 Oct 2021 20:21:38 -0400
Subject: [PATCH] RML 3-axis

---
 files.html                                       | 1 +
 programs/index.js                                | 1 +
 programs/machines/Roland/mill/SRM-20/mill 3D stl | 1 +
 3 files changed, 3 insertions(+)
 create mode 100644 programs/machines/Roland/mill/SRM-20/mill 3D stl

diff --git a/files.html b/files.html
index ba65f52..ab8fdc7 100644
--- a/files.html
+++ b/files.html
@@ -248,6 +248,7 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/Roland/mill/SRM-20/PCB%20svg%20connect'>PCB svg connect</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/Roland/mill/SRM-20/mill%202.5D%20stl'>mill 2.5D stl</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/Roland/mill/SRM-20/mill%202.5D%20stl%20connect'>mill 2.5D stl connect</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/Roland/mill/SRM-20/mill%203D%20stl'>mill 3D stl</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vinyl cutter</i><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GX-24</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/Roland/vinyl%20cutter/GX-24/cut%20png'>cut png</a><br>
diff --git a/programs/index.js b/programs/index.js
index 2066df4..c9d6abe 100644
--- a/programs/index.js
+++ b/programs/index.js
@@ -40,6 +40,7 @@ program_menu('            PCB svg','programs/machines/Roland/mill/SR
 program_menu('            PCB svg connect','programs/machines/Roland/mill/SRM-20/PCB%20svg%20connect')
 program_menu('            mill 2.5D stl','programs/machines/Roland/mill/SRM-20/mill%202.5D%20stl')
 program_menu('            mill 2.5D stl connect','programs/machines/Roland/mill/SRM-20/mill%202.5D%20stl%20connect')
+program_menu('            mill 3D stl','programs/machines/Roland/mill/SRM-20/mill%203D%20stl')
 program_label('      vinyl cutter')
 program_label('         GX-24')
 program_menu('            cut png','programs/machines/Roland/vinyl%20cutter/GX-24/cut%20png')
diff --git a/programs/machines/Roland/mill/SRM-20/mill 3D stl b/programs/machines/Roland/mill/SRM-20/mill 3D stl
new file mode 100644
index 0000000..97f43f6
--- /dev/null
+++ b/programs/machines/Roland/mill/SRM-20/mill 3D stl	
@@ -0,0 +1 @@
+{"modules":{"0.0622871075904039":{"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.97260818189417","left":"197.29145926127353","filename":"undefined","inputs":{},"outputs":{}},"0.908940842104825":{"definition":"//\n// mesh rotate\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 = 'mesh rotate'\n//\n// initialization\n//\nvar init = function() {\n   mod.rx.value = '0'\n   mod.ry.value = '0'\n   mod.rz.value = '0'\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = evt.detail\n         rotate_mesh()}}}\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\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   // rotation\n   //\n   div.appendChild(document.createTextNode('rotation (degrees):'))\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      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.rx = input\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.ry = input\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      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.rz = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // info\n   //\n   var text = document.createTextNode('dx:')\n      div.appendChild(text)\n      mod.dxn = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('dy:')\n      div.appendChild(text)\n      mod.dyn = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('dz:')\n      div.appendChild(text)\n      mod.dzn = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\n// rotate mesh\n//\nfunction rotate_mesh() {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(mod.mesh)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   //\n   // find limits, rotate, and draw\n   //\n   var blob = new Blob(['('+rotate_mesh_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      // mesh\n      //\n      mod.mesh = evt.data.mesh\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.rotate)\n      })\n   //\n   // call worker\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   var rx = parseFloat(mod.rx.value)*Math.PI/180\n   var ry = parseFloat(mod.ry.value)*Math.PI/180\n   var rz = parseFloat(mod.rz.value)*Math.PI/180\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      rx:rx,ry:ry,rz:rz,\n      image:img.data.buffer,mesh:mod.mesh},\n      [img.data.buffer,mod.mesh])\n   }\nfunction rotate_mesh_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      // function to rotate point\n      //\n      function rotate(x,y,z) {\n         var x1 = x\n         var y1 = Math.cos(rx)*y-Math.sin(rx)*z\n         var z1 = Math.sin(rx)*y+Math.cos(rx)*z\n         var x2 = Math.cos(ry)*x1-Math.sin(ry)*z1\n         var y2 = y1\n         var z2 = Math.sin(ry)*x1+Math.cos(ry)*z1\n         var x3 = Math.cos(rz)*x2-Math.sin(rz)*y2\n         var y3 = Math.sin(rz)*x2+Math.cos(rz)*y2\n         var z3 = z2\n         //return([x3,y3,z3])\n         return({x:x3,y:y3,z:z3})\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var rx = evt.data.rx\n      var ry = evt.data.ry\n      var rz = evt.data.rz\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         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         var p0 = rotate(x0,y0,z0)\n         if (p0.x > xmax) xmax = p0.x\n         if (p0.x < xmin) xmin = p0.x\n         if (p0.y > ymax) ymax = p0.y\n         if (p0.y < ymin) ymin = p0.y\n         if (p0.z > zmax) zmax = p0.z\n         if (p0.z < zmin) zmin = p0.z\n         var p1 = rotate(x1,y1,z1)\n         if (p1.x > xmax) xmax = p1.x\n         if (p1.x < xmin) xmin = p1.x\n         if (p1.y > ymax) ymax = p1.y\n         if (p1.y < ymin) ymin = p1.y\n         if (p1.z > zmax) zmax = p1.z\n         if (p1.z < zmin) zmin = p1.z\n         var p2 = rotate(x2,y2,z2)\n         if (p2.x > xmax) xmax = p2.x\n         if (p2.x < xmin) xmin = p2.x\n         if (p2.y > ymax) ymax = p2.y\n         if (p2.y < ymin) ymin = p2.y\n         if (p2.z > zmax) zmax = p2.z\n         if (p2.z < zmin) zmin = p2.z\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // copy mesh\n      //\n      var newbuf = evt.data.mesh.slice(0)\n      var newview = new DataView(newbuf)\n      //\n      // copy and 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      var newoffset = 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         var p0 = rotate(x0,y0,z0)\n         var p1 = rotate(x1,y1,z1)\n         var p2 = rotate(x2,y2,z2)\n         line(p0.x,p0.y,p1.x,p1.y)\n         line(p1.x,p1.y,p2.x,p2.y)\n         line(p2.x,p2.y,p0.x,p0.y)\n         newoffset += 3*4\n         newview.setFloat32(newoffset,p0.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p0.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p0.z,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.z,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.z,endian)\n         newoffset += 4\n         newoffset += 2\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,rotate:newbuf},\n         [evt.data.image,evt.data.mesh,newbuf])\n      self.close()\n      })\n   }\nfunction old_rotate_mesh() {\n   //\n   // function to rotate point\n   //\n   function rotate(x,y,z) {\n      var x1 = x\n      var y1 = Math.cos(rx)*y-Math.sin(rx)*z\n      var z1 = Math.sin(rx)*y+Math.cos(rx)*z\n      var x2 = Math.cos(ry)*x1-Math.sin(ry)*z1\n      var y2 = y1\n      var z2 = Math.sin(ry)*x1+Math.cos(ry)*z1\n      var x3 = Math.cos(rz)*x2-Math.sin(rz)*y2\n      var y3 = Math.sin(rz)*x2+Math.cos(rz)*y2\n      var z3 = z2\n      return([x3,y3,z3])\n      }\n   //\n   // get vars\n   //\n   var view = mod.mesh\n   var endian = true\n   var triangles = view.getUint32(80,endian)\n   mod.triangles = triangles\n   var size = 80+4+triangles*(4*12+2)\n   var rx = parseFloat(mod.rx.value)*Math.PI/180\n   var ry = parseFloat(mod.ry.value)*Math.PI/180\n   var rz = parseFloat(mod.rz.value)*Math.PI/180\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      var p0 = rotate(x0,y0,z0)\n      if (p0[0] > xmax) xmax = p0[0]\n      if (p0[0] < xmin) xmin = p0[0]\n      if (p0[1] > ymax) ymax = p0[1]\n      if (p0[1] < ymin) ymin = p0[1]\n      if (p0[2] > zmax) zmax = p0[2]\n      if (p0[2] < zmin) zmin = p0[2]\n      var p1 = rotate(x1,y1,z1)\n      if (p1[0] > xmax) xmax = p1[0]\n      if (p1[0] < xmin) xmin = p1[0]\n      if (p1[1] > ymax) ymax = p1[1]\n      if (p1[1] < ymin) ymin = p1[1]\n      if (p1[2] > zmax) zmax = p1[2]\n      if (p1[2] < zmin) zmin = p1[2]\n      var p2 = rotate(x2,y2,z2)\n      if (p2[0] > xmax) xmax = p2[0]\n      if (p2[0] < xmin) xmin = p2[0]\n      if (p2[1] > ymax) ymax = p2[1]\n      if (p2[1] < ymin) ymin = p2[1]\n      if (p2[2] > zmax) zmax = p2[2]\n      if (p2[2] < zmin) zmin = p2[2]\n      }\n   mod.dx = xmax-xmin\n   mod.dy = ymax-ymin\n   mod.dz = zmax-zmin\n   mod.dxn.nodeValue = 'dx: '+mod.dx.toFixed(3)\n   mod.dyn.nodeValue = 'dy: '+mod.dy.toFixed(3)\n   mod.dzn.nodeValue = 'dz: '+mod.dz.toFixed(3)\n   mod.xmin = xmin\n   mod.ymin = ymin\n   mod.zmin = zmin\n   mod.xmax = xmax\n   mod.ymax = ymax\n   mod.zmax = zmax\n   //\n   // copy mesh\n   //\n   var buf = mod.mesh.buffer.slice(0)\n   var newview = new DataView(buf)\n   //\n   // draw projection and save rotation\n   //\n   var ctx = mod.meshcanvas.getContext('2d')\n   var w = mod.meshcanvas.width\n   var h = mod.meshcanvas.height\n   ctx.clearRect(0,0,w,h)\n   var dx = mod.dx\n   var dy = mod.dy\n   if (dx > dy) {\n      var xo = 0\n      var yo = h*.5*(1-dy/dx)\n      var xw = w\n      var yh = w*dy/dx\n      }\n   else {\n      var xo = w*.5*(1-dx/dy)\n      var yo = 0\n      var xw = h*dx/dy\n      var yh = h\n      }\n   ctx.beginPath()\n   offset = 80+4\n   var newoffset = 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      var p0 = rotate(x0,y0,z0)\n      var p1 = rotate(x1,y1,z1)\n      var p2 = rotate(x2,y2,z2)\n      x0 = xo+xw*(p0[0]-xmin)/dx\n      y0 = yo+yh*(ymax-p0[1])/dy\n      x1 = xo+xw*(p1[0]-xmin)/dx\n      y1 = yo+yh*(ymax-p1[1])/dy\n      x2 = xo+xw*(p2[0]-xmin)/dx\n      y2 = yo+yh*(ymax-p2[1])/dy\n      ctx.moveTo(x0,y0)\n      ctx.lineTo(x1,y1)\n      ctx.lineTo(x2,y2)\n      ctx.lineTo(x0,y0)\n      newoffset += 3*4\n      newview.setFloat32(newoffset,p0[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p0[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p0[2],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[2],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[2],endian)\n      newoffset += 4\n      newoffset += 2\n      }\n   ctx.stroke()\n   //\n   // generate output\n   //\n   outputs.mesh.event(buf)\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":"221.9726081818942","left":"660.2914592612734","filename":"undefined","inputs":{},"outputs":{}},"0.373975449262817":{"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 = '1000'\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         obj.mmunits = mod.mmunits.value\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":"94.15172857805109","left":"1175.440248908119","filename":"undefined","inputs":{},"outputs":{}},"0.21976500984768532":{"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":"1020.8346976156153","left":"1197.1325110419543","filename":"undefined","inputs":{},"outputs":{}},"0.5958662931535647":{"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":"800.2368210589018","left":"1218.158731606769","filename":"undefined","inputs":{},"outputs":{}},"0.1219393240810458":{"definition":"//\n// mill raster 3D\n//\n// Neil Gershenfeld 10/22/21\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'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.125'\n   mod.dia_mm.value = '3.175'\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.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.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.mmunits = evt.detail.mmunits\n         mod.dpi = mod.width/(mod.mmunits*(mod.xmax-mod.xmin)/25.4)\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 = 'checkbox'\n      input.id = mod.div.id+'xz'\n      input.checked = true\n      div.appendChild(input)\n      mod.dirxz = input\n   div.appendChild(document.createTextNode('xz'))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'yz'\n      input.checked = true\n      div.appendChild(input)\n      mod.diryz = input\n   div.appendChild(document.createTextNode('yz'))\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      if (evt.data.type == \"progress\") {\n         mod.label.nodeValue = evt.data.value\n         return\n         }\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.height-1-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.height-1-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      height:mod.height,width:mod.width,\n      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      diameter:mod.dia_mm.value,\n      stepover:mod.stepover.value,\n      mmunits: mod.mmunits,\n      xz:mod.dirxz.checked,\n      yz:mod.diryz.checked,\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.height\n      var w = evt.data.width\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 diameter = evt.data.diameter\n      var stepover = evt.data.stepover\n      var mmunits = evt.data.mmunits\n      var step = Math.floor(stepover*diameter*w/(mmunits*(xmax-xmin)))\n      var path = [[]]\n      //\n      // construct tool offset\n      //\n      var toolsize = Math.floor(diameter*w/(mmunits*(xmax-xmin)))\n      var tooloffset = new Float32Array(toolsize*toolsize)\n      var toolmiddle = Math.floor(toolsize/2)\n      for (var x = 0; x < toolsize; ++x)\n         for (var y = 0; y < toolsize; ++y) {\n            var r = Math.sqrt((x-toolmiddle)*(x-toolmiddle)+(y-toolmiddle)*(y-toolmiddle))\n            if (r <= toolmiddle)\n               tooloffset[y*toolsize+x] = 0\n            else\n               tooloffset[y*toolsize+x] = Number.MAX_VALUE\n            }\n      //\n      // xz\n      //\n      if (evt.data.xz) {\n         var xstart = 0\n         var ystart = h-1\n         var zstart = Math.floor((map[ystart*w+xstart]-zmax)*w/(xmax-xmin))\n         path[0].push([xstart,h-1-ystart,zstart])\n         var xcur = 1\n         var ycur = h-1\n         var zcur = Math.floor((map[ycur*w+xcur]-zmax)*w/(xmax-xmin))\n         var dx = 1\n         var dy = 0\n         while (1) {\n            //\n            // move to next point\n            //\n            xnext = xcur+dx\n            ynext = ycur+dy\n            if (ynext <= 0)\n               //\n               // done\n               //\n               break;\n            //\n            // find offset at next point\n            //\n            var znext = -Number.MAX_VALUE\n            for (var xoffset = 0; xoffset < toolsize; ++xoffset)\n               for (var yoffset = 0; yoffset < toolsize; ++yoffset) {\n                  var x = xnext+(xoffset-toolmiddle)\n                  var y = ynext+(yoffset-toolmiddle)\n                  var offset = tooloffset[yoffset*toolsize+xoffset]\n                  var z = Math.floor((map[y*w+x]-zmax)*w/(xmax-xmin))-offset\n                  if (z > znext)\n                     znext = z\n                  }\n            //\n            // vectorize\n            //\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,h-1-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 = -step\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 = -step\n                  }\n               else {\n                  dx = 1\n                  dy = 0\n                  }\n               }\n            //\n            // update progress\n            //\n            self.postMessage({type:'progress',value:ycur})\n            }\n         }\n      //\n      // yz\n      //\n      if (evt.data.yz) {\n         var xstart = w-1\n         var ystart = 0\n         var zstart = Math.floor((map[ystart*w+xstart]-zmax)*w/(xmax-xmin))\n         path[0].push([xstart,h-1-ystart,zstart])\n         var xcur = w-1\n         var ycur = 1\n         var zcur = Math.floor((map[ycur*w+xcur]-zmax)*w/(xmax-xmin))\n         var dx = 0\n         var dy = 1\n         while (1) {\n            //\n            // move to next point\n            //\n            xnext = xcur+dx\n            ynext = ycur+dy\n            if (xnext <= 0)\n               //\n               // done\n               //\n               break;\n            //\n            // find offset at next point\n            //\n            var znext = -Number.MAX_VALUE\n            for (var xoffset = 0; xoffset < toolsize; ++xoffset)\n               for (var yoffset = 0; yoffset < toolsize; ++yoffset) {\n                  var x = xnext+(xoffset-toolmiddle)\n                  var y = ynext+(yoffset-toolmiddle)\n                  var offset = tooloffset[yoffset*toolsize+xoffset]\n                  var z = Math.floor((map[y*w+x]-zmax)*w/(xmax-xmin))-offset\n                  if (z > znext)\n                     znext = z\n                  }\n            //\n            // vectorize\n            //\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,h-1-ycur,zcur])\n               xstart = xcur\n               ystart = ycur\n               zstart = zcur\n               }\n            xcur = xnext\n            ycur = ynext\n            zcur = znext\n            if (ycur == (w-1)) {\n               if (dy == 1) {\n                  dy = 0\n                  dx = -step\n                  }\n               else {\n                  dy = -1\n                  dx = 0\n                  }\n               }\n            else if (ycur == 0) {\n               if (dy == -1) {\n                  dy = 0\n                  dx = -step\n                  }\n               else {\n                  dy = 1\n                  dx = 0\n                  }\n               }\n            //\n            // update progress\n            //\n            self.postMessage({type:'progress',value:xcur})\n            }\n         }\n      //\n      // return\n      //\n      self.postMessage({type:'path',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":"762.7682149396354","left":"224.94074215439025","filename":"modules/processes/mill/raster/3D","inputs":{},"outputs":{}},"0.3062798763523076":{"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   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(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":"950.826050590963","left":"644.0956411073677","filename":"modules/path/machines/Roland/milling/SRM-20","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.0622871075904039\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.908940842104825\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.908940842104825\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.373975449262817\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.373975449262817\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"map\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.1219393240810458\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"map\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.1219393240810458\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.21976500984768532\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.1219393240810458\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3062798763523076\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3062798763523076\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5958662931535647\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
\ No newline at end of file
-- 
GitLab