diff --git a/files.html b/files.html
index c14da857ce260855dcb47d22a08066c5e7e98a2e..e392e389c0e2ced3922a352e8f01c63bbb20f9c2 100644
--- a/files.html
+++ b/files.html
@@ -10,7 +10,6 @@
    </script>
    <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.git</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./.gitignore'>.gitignore</a><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./README.md'>README.md</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./files.html'>files.html</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./index.html'>index.html</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;js</i><br>
@@ -19,7 +18,6 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/files.js'>files.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/mods.js'>mods.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/modules.js'>modules.js</a><br>
-<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node_modules</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/printserver.js'>printserver.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/programs.js'>programs.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/serialserver.js'>serialserver.js</a><br>
@@ -27,6 +25,7 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/three.js/three.min.js'>three.min.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/udpserver.js'>udpserver.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./make'>make</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./make.bat'>make.bat</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;modules</i><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;character</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/character/convert'>convert</a><br>
@@ -34,8 +33,11 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/character/parse'>parse</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/character/variable'>variable</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connect</i><br>
+<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stl</i><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/connect/stl/STL'>STL</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;svg</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/connect/svg/ExtractFaces'>ExtractFaces</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;<a href='./modules/connect/svg/Honeycomb'>Honeycomb</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;<a href='./modules/connect/svg/PCB'>PCB</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;<a href='./modules/connect/svg/SelectFace'>SelectFace</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;convert</i><br>
@@ -154,6 +156,8 @@
 &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='./modules/path/formats/g-code'>g-code</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;<a href='./modules/path/formats/svg'>svg</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;machines</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;laser cutter</i><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/path/machines/laser%20cutter/Epilog'>Epilog</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;Roland</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;milling</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='./modules/path/machines/Roland/milling/MDX-20'>MDX-20</a><br>
@@ -161,8 +165,6 @@
 <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;vinyl cutter</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/path/machines/Roland/vinyl%20cutter/GX-24'>GX-24</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;<a href='./modules/path/machines/ShopBot'>ShopBot</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;laser cutter</i><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/path/machines/laser%20cutter/Epilog'>Epilog</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./modules/path/view'>view</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;processes</i><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cut</i><br>
@@ -215,8 +217,11 @@
 &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/Epilog/cut%20svg%20connect'>cut svg connect</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;G-code</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/G-code/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;<a href='./programs/machines/G-code/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;<a href='./programs/machines/G-code/mill%202D%20png'>mill 2D png</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;<a href='./programs/machines/G-code/mill%202D%20svg'>mill 2D svg</a><br>
+<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RNDMC</i><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/RNDMC/honeycomb%20connect'>honeycomb connect</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Roland</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;mill</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;MDX-20</i><br>
@@ -224,10 +229,11 @@
 &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/MDX-20/PCB%20png'>PCB png</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/MDX-20/PCB%20svg'>PCB svg</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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SRM-20</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/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/PCB%20png'>PCB png</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/PCB%20svg'>PCB svg</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/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>
 <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>
@@ -235,6 +241,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/vinyl%20cutter/GX-24/cut%20svg%20connect'>cut svg connect</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ShopBot</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/machines/ShopBot/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;<a href='./programs/machines/ShopBot/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;<a href='./programs/machines/ShopBot/mill%202D%20png'>mill 2D png</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;<a href='./programs/machines/ShopBot/mill%202D%20png%20PCB'>mill 2D png PCB</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;<a href='./programs/machines/ShopBot/mill%202D%20stl'>mill 2D stl</a><br>
@@ -253,25 +260,25 @@
 &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/processes/mill/raster/2D'>2D</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;variable</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/variable/text%20variables'>text variables</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./README.md'>README.md</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scripts</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./scripts/start%20mods%20server'>start mods server</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./scripts/stop%20mods%20server'>stop mods server</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/3DBenchy.stl'>3DBenchy.stl</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/alien.png'>alien.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/ATP.8E5.board.png'>ATP.8E5.board.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/ATP.8E5.traces.png'>ATP.8E5.traces.png</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/cert.pem'>cert.pem</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/David.png'>David.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/David.small.png'>David.small.png</a><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/ML.jpg'>ML.jpg</a><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/ML.png'>ML.png</a><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/Suzanne.stl'>Suzanne.stl</a><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/alien.png'>alien.png</a><br>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/cert.pem'>cert.pem</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/gradients.svg'>gradients.svg</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/hmc-3dp3-01.stl'>hmc-3dp3-01.stl</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/hsv.png'>hsv.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/key.pem'>key.pem</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/lines.png'>lines.png</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/ML.jpg'>ML.jpg</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/ML.png'>ML.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/rhino.black.png'>rhino.black.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/rhino.black.svg'>rhino.black.svg</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/rhino.png'>rhino.png</a><br>
@@ -286,6 +293,7 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/shapes.svg'>shapes.svg</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/sunset.jpg'>sunset.jpg</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/sunset.png'>sunset.png</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/Suzanne.stl'>Suzanne.stl</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/tall.png'>tall.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/test.png'>test.png</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./test/transparent.png'>transparent.png</a><br>
diff --git a/modules/connect/stl/STL b/modules/connect/stl/STL
new file mode 100644
index 0000000000000000000000000000000000000000..682be0071acdf1614de966bedfc25a9a8c82e7a2
--- /dev/null
+++ b/modules/connect/stl/STL
@@ -0,0 +1,461 @@
+//
+// STL module extracts stl from Tools/FabLab Connect command of SolidWorks products
+// 
+// Shawn Liu @ Dassault Systemes SolidWorks Corporation
+// (c) Massachusetts Institute of Technology 2019
+// 
+// This work may be reproduced, modified, distributed, performed, and 
+// displayed for any purpose, but must acknowledge the mods
+// project. Copyright is retained and must be preserved. The work is 
+// provided as is; no warranty is provided, and users accept all 
+// liability.
+//
+// closure
+//
+(function(){
+//
+// module globals
+//
+var mod = {}
+//
+// name
+//
+var name = 'STL connect'
+//
+// initialization
+//
+var init = function () {
+    mod.address = getParameterByName('swIP') || '127.0.0.1'
+    mod.port = getParameterByName('swPort') || '80'
+    mod.socket = 0
+    mod.sag.value = '0.1'
+    mod.angle.value = '45'
+    socket_open()
+   }
+//
+// inputs
+//
+var inputs = {
+   }
+//
+// outputs
+//
+var outputs = {
+   mesh:{type:'STL',
+      event:function(buffer){
+         mods.output(mod,'mesh',buffer)}}
+      }
+//
+// interface
+//
+var interface = function(div){
+   mod.div = div
+   //
+   // canvas
+   //
+   var canvas = document.createElement('canvas')
+      canvas.width = mods.ui.canvas
+      canvas.height = mods.ui.canvas
+      canvas.style.backgroundColor = 'rgb(255,255,255)'
+      div.appendChild(canvas)
+      mod.canvas = canvas
+      div.appendChild(document.createElement('br'))
+
+      div.appendChild(document.createTextNode('server:'))
+      div.appendChild(document.createElement('br'))
+      div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))
+      div.appendChild(document.createElement('br'))
+      div.appendChild(document.createTextNode('\u00a0\u00a0\u00a0\u00a0\u00a0port: ' + getParameterByName('swPort')))
+      div.appendChild(document.createElement('br'))
+      div.appendChild(document.createTextNode('\u00a0\u00a0status: '))
+      input = document.createElement('input')
+      input.type = 'text'
+      input.size = 12
+      div.appendChild(input)
+      mod.status = input
+      div.appendChild(document.createElement('br'))
+      var btn = document.createElement('button')
+      btn.style.margin = 1
+      btn.appendChild(document.createTextNode('open'))
+      btn.addEventListener('click', function () {
+          socket_open()
+      })
+      div.appendChild(btn)
+      var btn = document.createElement('button')
+      btn.style.margin = 1
+      btn.appendChild(document.createTextNode('close'))
+      btn.addEventListener('click', function () {
+          socket_close()
+      })
+      div.appendChild(btn)
+      div.appendChild(document.createElement('br'))
+      div.appendChild(document.createTextNode('sag: '))
+      input = document.createElement('input')
+      input.type = 'text'
+      input.size = 10
+      div.appendChild(input)
+      mod.sag = input
+      div.appendChild(document.createTextNode('(mm)'))
+      div.appendChild(document.createElement('br'))
+      div.appendChild(document.createTextNode('angle: '))
+      input = document.createElement('input')
+      input.type = 'text'
+      input.size = 10
+      div.appendChild(input)
+      mod.angle = input
+      div.appendChild(document.createTextNode('(degree)'))
+      div.appendChild(document.createElement('br'))
+      var btn = document.createElement('button')
+      btn.style.margin = 1
+      btn.appendChild(document.createTextNode('Extract STL'))
+      btn.addEventListener('click', function () {
+          extract_STL()
+      })
+      div.appendChild(btn)
+
+   //
+   // info
+   //
+   var info = document.createElement('div')
+   info.setAttribute('id', div.id + 'info')
+       var text = document.createTextNode('name: ')
+       info.appendChild(text)
+       mod.name = text
+       info.appendChild(document.createElement('br'))
+      var text = document.createTextNode('size: ')
+         info.appendChild(text)
+         mod.sizen = text
+      info.appendChild(document.createElement('br'))
+      var text = document.createTextNode('triangles: ')
+         info.appendChild(text)
+         mod.trianglesn = text
+      info.appendChild(document.createElement('br'))
+      var text = document.createTextNode('dx: ')
+         info.appendChild(text)
+         mod.dxn = text
+      info.appendChild(document.createElement('br'))
+      var text = document.createTextNode('dy: ')
+         info.appendChild(text)
+         mod.dyn = text
+      info.appendChild(document.createElement('br'))
+      var text = document.createTextNode('dz: ')
+         info.appendChild(text)
+         mod.dzn = text
+         div.appendChild(info)
+   }
+//
+// local functions
+//
+
+    //
+    // local functions
+    //
+
+function getParameterByName(name, url) {
+    if (!url) url = window.location.href;
+    name = name.replace(/[\[\]]/g, "\\$&");
+    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+        results = regex.exec(url);
+    if (!results) return null;
+    if (!results[2]) return '';
+    return decodeURIComponent(results[2].replace(/\+/g, " "));
+}
+
+function socket_open() {
+    var url = "ws://" + mod.address + ':' + mod.port
+    mod.socket = new WebSocket(url)
+    mod.socket.onopen = function (event) {
+        mod.status.value = "opened"
+        var connect = {}
+        connect.modCmd = 'connect'
+        connect.owner = getParameterByName('swOwner')
+        connect.id = getParameterByName('swID')
+        socket_send(JSON.stringify(connect))
+    }
+    mod.socket.onerror = function (event) {
+        mod.status.value = "can not open"
+    }
+    mod.socket.onmessage = function (event) {
+        mod.status.value = "receive"
+        var swData = JSON.parse(event.data)
+        if (swData.swType === "AutoExtractSTL") {
+            var partName = swData.data.partName
+            if (partName.length > 25)
+                partName = partName.slice(0, 22) +'...'
+            mod.name.nodeValue = "name: " + partName
+            mod.str = swData.data.stl
+            stl_load_handler()
+        }
+    }
+    mod.socket.onclose = function (event) {
+        mod.status.value = "connection closed"
+    }
+}
+function socket_close() {
+    mod.socket.close()
+    mod.status.value = "closed"
+    mod.socket = 0
+}
+function socket_send(msg) {
+    if (mod.socket != 0) {
+        mod.status.value = "send"
+        mod.socket.send(msg)
+    }
+    else {
+        mod.status.value = "can't send, not open"
+    }
+}
+function extract_STL() {
+    var modcmd = new Object;
+    modcmd.modCmd = "AutoExtractSTL";
+    modcmd.sag = Number(mod.sag.value); // mm
+    modcmd.angle = Number(mod.angle.value); //degree
+    socket_send(JSON.stringify(modcmd))
+}
+
+function base64ToArrayBuffer(base64) {
+    var binary_string = window.atob(base64);
+    var len = binary_string.length;
+    var bytes = new Uint8Array(len);
+    for (var i = 0; i < len; i++) {
+        bytes[i] = binary_string.charCodeAt(i);
+    }
+    return bytes.buffer;
+}
+
+//
+// load handler
+//
+function stl_load_handler() {
+   //
+   // check for binary STL
+    //
+   var arraybuf = base64ToArrayBuffer(mod.str)
+   var endian = true
+   var view = new DataView(arraybuf)
+   var triangles = view.getUint32(80, endian)
+   var size = 80+4+triangles*(4*12+2)
+   if (size != view.byteLength) {
+      mod.sizen.nodeValue = 'error: not binary STL'
+      mod.trianglesn.nodeValue = ''
+      mod.dxn.nodeValue = ''
+      mod.dyn.nodeValue = ''
+      mod.dzn.nodeValue = ''
+      return
+      }
+   mod.sizen.nodeValue = 'size: '+size
+   mod.trianglesn.nodeValue = 'triangles: '+triangles
+   //
+   // find limits and draw
+   //
+   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])
+   var url = window.URL.createObjectURL(blob)
+   var webworker = new Worker(url)
+   webworker.addEventListener('message',function(evt) {
+      //
+      // worker response
+      //
+      window.URL.revokeObjectURL(url)
+      //
+      // size
+      //
+      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)
+      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)
+      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)
+      //
+      // image
+      //
+      var image = evt.data.image
+      var height = mod.canvas.height
+      var width = mod.canvas.width
+      var buffer = new Uint8ClampedArray(evt.data.image)
+      var imgdata = new ImageData(buffer,width,height)
+      var ctx = mod.canvas.getContext("2d")
+      ctx.putImageData(imgdata,0,0)
+      //
+      // output
+      //
+      outputs.mesh.event(evt.data.mesh)
+      })
+   var ctx = mod.canvas.getContext("2d")
+   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)
+   var img = ctx.getImageData(0, 0, mod.canvas.width, mod.canvas.height)
+   //
+   // call worker
+   //
+   webworker.postMessage({
+      height:mod.canvas.height,width:mod.canvas.width,
+      image: img.data.buffer, mesh: arraybuf},
+      [img.data.buffer, arraybuf])
+   }
+function draw_limits_worker() {
+   self.addEventListener('message',function(evt) {
+      //
+      // function to draw line
+      //
+      function line(x0,y0,x1,y1) {
+         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)
+         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)
+         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)
+         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)
+         var row,col
+         var idx = ix1-ix0
+         var idy = iy1-iy0
+         if (Math.abs(idy) > Math.abs(idx)) {
+            (idy > 0) ?
+               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):
+               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)
+            for (row = row0; row <= row1; ++row) {
+               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))
+               image[row*width*4+col*4+0] = 0
+               image[row*width*4+col*4+1] = 0
+               image[row*width*4+col*4+2] = 0
+               image[row*width*4+col*4+3] = 255
+               }
+            }
+         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {
+            (idx > 0) ?
+               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):
+               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)
+            for (col = col0; col <= col1; ++col) {
+               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))
+               image[row*width*4+col*4+0] = 0
+               image[row*width*4+col*4+1] = 0
+               image[row*width*4+col*4+2] = 0
+               image[row*width*4+col*4+3] = 255
+               }
+            }
+         else {
+            row = iy0
+            col = ix0
+            image[row*width*4+col*4+0] = 0
+            image[row*width*4+col*4+1] = 0
+            image[row*width*4+col*4+2] = 0
+            image[row*width*4+col*4+3] = 255
+            }
+         }
+      //
+      // get variables
+      //
+      var height = evt.data.height
+      var width = evt.data.width
+      var endian = true
+      var image = new Uint8ClampedArray(evt.data.image)
+      var view = new DataView(evt.data.mesh)
+      var triangles = view.getUint32(80,endian)
+      //
+      // find limits
+      //
+      var offset = 80+4
+      var x0,x1,x2,y0,y1,y2,z0,z1,z2
+      var xmin = Number.MAX_VALUE
+      var xmax = -Number.MAX_VALUE
+      var ymin = Number.MAX_VALUE
+      var ymax = -Number.MAX_VALUE
+      var zmin = Number.MAX_VALUE
+      var zmax = -Number.MAX_VALUE
+      for (var t = 0; t < triangles; ++t) {
+         offset += 3*4
+         x0 = view.getFloat32(offset,endian)
+         offset += 4
+         if (x0 > xmax) xmax = x0
+         if (x0 < xmin) xmin = x0
+         y0 = view.getFloat32(offset,endian)
+         offset += 4
+         if (y0 > ymax) ymax = y0
+         if (y0 < ymin) ymin = y0
+         z0 = view.getFloat32(offset,endian)
+         offset += 4
+         if (z0 > zmax) zmax = z0
+         if (z0 < zmin) zmin = z0
+         x1 = view.getFloat32(offset,endian)
+         offset += 4
+         if (x1 > xmax) xmax = x1
+         if (x1 < xmin) xmin = x1
+         y1 = view.getFloat32(offset,endian)
+         offset += 4
+         if (y1 > ymax) ymax = y1
+         if (y1 < ymin) ymin = y1
+         z1 = view.getFloat32(offset,endian)
+         offset += 4
+         if (z1 > zmax) zmax = z1
+         if (z1 < zmin) zmin = z1
+         x2 = view.getFloat32(offset,endian)
+         offset += 4
+         if (x2 > xmax) xmax = x2
+         if (x2 < xmin) xmin = x2
+         y2 = view.getFloat32(offset,endian)
+         offset += 4
+         if (y2 > ymax) ymax = y2
+         if (y2 < ymin) ymin = y2
+         z2 = view.getFloat32(offset,endian)
+         offset += 4
+         if (z2 > zmax) zmax = z2
+         if (z2 < zmin) zmin = z2
+         offset += 2
+         }
+      var dx = xmax-xmin
+      var dy = ymax-ymin
+      var dz = zmax-zmin
+      //
+      // draw mesh
+      //
+      if (dx > dy) {
+         var xo = 0
+         var yo = height*.5*(1-dy/dx)
+         var xw = width-1
+         var yh = (width-1)*dy/dx
+         }
+      else {
+         var xo = width*.5*(1-dx/dy)
+         var yo = 0
+         var xw = (height-1)*dx/dy
+         var yh = height-1
+         }
+      offset = 80+4
+      for (var t = 0; t < triangles; ++t) {
+         offset += 3*4
+         x0 = view.getFloat32(offset,endian)
+         offset += 4
+         y0 = view.getFloat32(offset,endian)
+         offset += 4
+         z0 = view.getFloat32(offset,endian)
+         offset += 4
+         x1 = view.getFloat32(offset,endian)
+         offset += 4
+         y1 = view.getFloat32(offset,endian)
+         offset += 4
+         z1 = view.getFloat32(offset,endian)
+         offset += 4
+         x2 = view.getFloat32(offset,endian)
+         offset += 4
+         y2 = view.getFloat32(offset,endian)
+         offset += 4
+         z2 = view.getFloat32(offset,endian)
+         offset += 4
+         offset += 2
+         line(x0,y0,x1,y1)
+         line(x1,y1,x2,y2)
+         line(x2,y2,x0,y0)
+         }
+      //
+      // return results and close
+      //
+      self.postMessage({
+         dx:dx,dy:dy,dz:dz,
+         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])
+      self.close()
+      })
+   }
+//
+// return values
+//
+return ({
+   mod:mod,
+   name:name,
+   init:init,
+   inputs:inputs,
+   outputs:outputs,
+   interface:interface
+   })
+}())
diff --git a/modules/connect/svg/Honeycomb b/modules/connect/svg/Honeycomb
new file mode 100644
index 0000000000000000000000000000000000000000..53e52f4357d3c364e93bce082ec438f5ba1c4025
--- /dev/null
+++ b/modules/connect/svg/Honeycomb
@@ -0,0 +1,317 @@
+//
+// Honeycomb module extracts flatten honeycomb sheet from Tools/FabLab Connect command of SolidWorks products
+// 
+// Shawn Liu @ Dassault Systemes SolidWorks Corporation
+// (c) Massachusetts Institute of Technology 2019
+// 
+// This work may be reproduced, modified, distributed, performed, and 
+// displayed for any purpose, but must acknowledge the mods
+// project. Copyright is retained and must be preserved. The work is 
+// provided as is; no warranty is provided, and users accept all 
+// liability.
+//
+// closure
+//
+(function(){
+//
+// module globals
+//
+var mod = {}
+//
+// name
+//
+var name = 'Honeycomb connect'
+//
+// initialization
+//
+var init = function() {
+   mod.address = getParameterByName('swIP') || '127.0.0.1'
+   mod.port = getParameterByName('swPort') || '80'
+   mod.socket = 0
+   socket_open()
+   }
+//
+// inputs
+//
+var inputs = {}
+//
+// outputs
+//
+var outputs = {
+    SVG: {
+        type: 'string',
+        event: function () {
+            mods.output(mod, 'SVG', mod.str)
+        }
+    },
+    file: {
+        type: 'object',
+        event: function (str) {
+            obj = {}
+            obj.name = mod.partName + ".svg"
+            obj.contents = str
+            mods.output(mod, 'file', obj)
+        }
+    }
+}
+//
+// interface
+//
+var interface = function(div){
+   mod.div = div
+
+    // on-screen drawing canvas
+    //
+    var canvas = document.createElement('canvas')
+    canvas.width = mods.ui.canvas
+    canvas.height = mods.ui.canvas
+    canvas.style.backgroundColor = 'rgb(255,255,255)'
+    div.appendChild(canvas)
+    mod.canvas = canvas
+    div.appendChild(document.createElement('br'))
+    //
+    // off-screen image canvas
+    //
+    var canvas = document.createElement('canvas')
+    mod.img = canvas
+
+   div.appendChild(document.createTextNode('server:'))
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('\u00a0\u00a0\u00a0\u00a0\u00a0port: ' + getParameterByName('swPort')))
+   div.appendChild(document.createElement('br'))
+   div.appendChild(document.createTextNode('\u00a0\u00a0status: '))
+   input = document.createElement('input')
+   input.type = 'text'
+   input.size = 12
+   div.appendChild(input)
+   mod.status = input
+   div.appendChild(document.createElement('br'))
+   var btn = document.createElement('button')
+   btn.style.margin = 1
+   btn.appendChild(document.createTextNode('open'))
+   btn.addEventListener('click', function () {
+       socket_open()
+   })
+   div.appendChild(btn)
+   var btn = document.createElement('button')
+   btn.style.margin = 1
+   btn.appendChild(document.createTextNode('close'))
+   btn.addEventListener('click', function () {
+       socket_close()
+   })
+   div.appendChild(btn)
+   div.appendChild(document.createElement('br'))
+   var btn = document.createElement('button')
+   btn.style.margin = 1
+   btn.appendChild(document.createTextNode('Extract SVG'))
+   btn.addEventListener('click', function () {
+       extract_SVG()
+   })
+   div.appendChild(btn)
+   div.appendChild(document.createElement('br'))
+    //
+    // view button
+    //
+   var btn = document.createElement('button')
+   btn.style.padding = mods.ui.padding
+   btn.style.margin = 1
+   btn.appendChild(document.createTextNode('view'))
+   btn.addEventListener('click', function () {
+       var win = window.open('')
+       var btn = document.createElement('button')
+       btn.appendChild(document.createTextNode('close'))
+       btn.style.padding = mods.ui.padding
+       btn.style.margin = 1
+       btn.addEventListener('click', function () {
+           win.close()
+       })
+       win.document.body.appendChild(btn)
+       win.document.body.appendChild(document.createElement('br'))
+       var canvas = document.createElement('canvas')
+       canvas.width = mod.img.width
+       canvas.height = mod.img.height
+       win.document.body.appendChild(canvas)
+       var ctx = canvas.getContext("2d")
+       ctx.drawImage(mod.img, 0, 0)
+   })
+   div.appendChild(btn)
+   div.appendChild(document.createElement('br'))
+
+    //
+    // info div
+    //
+   var info = document.createElement('div')
+   info.setAttribute('id', div.id + 'info')
+   mod.name = document.createTextNode('name:')
+   info.appendChild(mod.name)
+   mod.thickness = document.createTextNode('thickness: ')
+   div.appendChild(mod.thickness)
+   info.appendChild(document.createElement('br'))
+   mod.width = document.createTextNode('width:')
+   info.appendChild(mod.width)
+   info.appendChild(document.createElement('br'))
+   mod.height = document.createTextNode('height:')
+   info.appendChild(mod.height)
+   div.appendChild(info)
+
+   }
+//
+// local functions
+//
+
+function getParameterByName(name, url) {
+    if (!url) url = window.location.href;
+    name = name.replace(/[\[\]]/g, "\\$&");
+    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+        results = regex.exec(url);
+    if (!results) return null;
+    if (!results[2]) return '';
+    return decodeURIComponent(results[2].replace(/\+/g, " "));
+}
+
+function socket_open() {
+   var url = "ws://"+mod.address+':'+mod.port
+   mod.socket = new WebSocket(url)
+   mod.socket.onopen = function(event) {
+       mod.status.value = "opened"
+       var connect = {}
+       connect.modCmd = 'connect'
+       connect.owner = getParameterByName('swOwner')
+       connect.id = getParameterByName('swID')
+       socket_send(JSON.stringify(connect))
+      }
+   mod.socket.onerror = function(event) {
+      mod.status.value = "can not open"
+      }
+   mod.socket.onmessage = function(event) {
+      mod.status.value = "receive"
+      var swData = JSON.parse(event.data);
+      if (swData.swType === "HoneycombFlattenSVG") {
+          mod.partName = swData.data.partName
+          var partName = swData.data.partName
+          if (partName.length > 25)
+              partName = partName.slice(0, 22) + '...'
+          mod.name.nodeValue = "name: " + partName
+          mod.thickness.nodeValue = "thickness: " + swData.data.thickness + ' (mm)';
+          mod.str = swData.data.svg
+          svg_load_handler()
+          outputs.SVG.event(mod.str)
+          outputs.file.event(mod.str)
+      }
+   }
+   mod.socket.onclose = function (event) {
+       mod.status.value = "connection closed"
+   }
+   }
+function socket_close() {
+   mod.socket.close()
+   mod.status.value = "closed"
+   mod.socket = 0
+   }
+function socket_send(msg) {
+   if (mod.socket != 0) {
+      mod.status.value = "send"
+      mod.socket.send(msg)
+      }
+   else {
+      mod.status.value = "can't send, not open"
+      }
+   }
+function extract_SVG() {
+   var modcmd = new Object;
+   modcmd.modCmd = "AutoHoneycombFlatten";
+   socket_send(JSON.stringify(modcmd))
+   }
+
+    //
+    // load handler
+    //
+function svg_load_handler() {
+    //
+    // parse size
+    //
+    var i = mod.str.indexOf("width")
+    if (i == -1) {
+        mod.width.nodeValue = "width: not found"
+        mod.height.nodeValue = "height: not found"
+    }
+    else {
+        var i1 = mod.str.indexOf("\"", i + 1)
+        var i2 = mod.str.indexOf("\"", i1 + 1)
+        var width = mod.str.substring(i1 + 1, i2)
+        i = mod.str.indexOf("height")
+        i1 = mod.str.indexOf("\"", i + 1)
+        i2 = mod.str.indexOf("\"", i1 + 1)
+        var height = mod.str.substring(i1 + 1, i2)
+        ih = mod.str.indexOf("height")
+        if (width.indexOf("px") != -1) {
+            width = width.slice(0, -2)
+            height = height.slice(0, -2)
+            var units = 90
+        }
+        else if (width.indexOf("mm") != -1) {
+            width = width.slice(0, -2)
+            height = height.slice(0, -2)
+            var units = 25.4
+        }
+        else if (width.indexOf("cm") != -1) {
+            width = width.slice(0, -2)
+            height = height.slice(0, -2)
+            var units = 2.54
+        }
+        else if (width.indexOf("in") != -1) {
+            width = width.slice(0, -2)
+            height = height.slice(0, -2)
+            var units = 1
+        }
+        else {
+            var units = 90
+        }
+        mod.width.nodeValue = "width: " + width / 3.543307 + ' (mm)';
+        mod.height.nodeValue = "height: " + height / 3.543307 + ' (mm)';
+    }
+    //
+    // display
+    //
+    var img = new Image()
+    var src = "data:image/svg+xml;base64," + window.btoa(mod.str)
+    img.setAttribute("src", src)
+    img.onload = function () {
+        if (img.width > img.height) {
+            var x0 = 0
+            var y0 = mod.canvas.height * .5 * (1 - img.height / img.width)
+            var w = mod.canvas.width
+            var h = mod.canvas.width * img.height / img.width
+        }
+        else {
+            var x0 = mod.canvas.width * .5 * (1 - img.width / img.height)
+            var y0 = 0
+            var w = mod.canvas.height * img.width / img.height
+            var h = mod.canvas.height
+        }
+        var ctx = mod.canvas.getContext("2d")
+        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)
+        ctx.drawImage(img, x0, y0, w, h)
+        var ctx = mod.img.getContext("2d")
+        ctx.canvas.width = img.width
+        ctx.canvas.height = img.height
+        ctx.drawImage(img, 0, 0)
+        outputs.SVG.event()
+    }
+}
+
+
+//
+// return values
+//
+return ({
+   mod:mod,
+   name:name,
+   init:init,
+   inputs:inputs,
+   outputs:outputs,
+   interface:interface
+   })
+}())
diff --git a/modules/convert/svg/array b/modules/convert/svg/array
index 9d5c63b335827c4bb7997cc465fde97ac855c9f3..81110c7424974e20a059d44a80f1d995d0e2bd8e 100644
--- a/modules/convert/svg/array
+++ b/modules/convert/svg/array
@@ -16,7 +16,9 @@
 //
 // module globals
 //
-var mod = {
+var mod = {}
+
+var modParam = {
   'parameters':{
       'stock_width':50.,
       'stock_height':24.,
@@ -30,9 +32,10 @@ var name = 'nest SVG Array'
 // initialization
 //
 var init = function() {
-   Object.keys(mod.parameters).forEach( function(k){
-      mod[k].value = mod.parameters[k]; //set default values
-   });
+    mod.stock_width.value = modParam.parameters.stock_width;
+    mod.stock_height.value = modParam.parameters.stock_height;
+    mod.padding.value = modParam.parameters.padding;
+
    }
 //
 // inputs
@@ -75,7 +78,7 @@ var interface = function(div){
    mod.bigview = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 
 
    //add parameter inputs
-   Object.keys(mod.parameters).forEach(function(p){
+   Object.keys(modParam.parameters).forEach(function (p) {
     var textnode = document.createElement('span');
     textnode.innerHTML = p+': ';
     textnode.style="display: inline-block; width: 80px; font-size: 12px;";
@@ -108,7 +111,7 @@ var interface = function(div){
       //max text
       input_max.type='text';
       input_max.size=2;
-      input_max.value = 2*mod.parameters[p]; //set initial maximum to twice default value
+      input_max.value = 2 * modParam.parameters[p]; //set initial maximum to twice default value
       input_max.addEventListener('blur',function(){
          input_range.value = 100 * input_text.value / input_max.value;
          input_text.value = Math.min( input_text.value, input_max.value );  
@@ -156,6 +159,7 @@ var interface = function(div){
 // local functions
 
 function nest(sw_json){
+   if (!sw_json) return
    //sw_json is text json exported from soliworks of the form [{partTitle:”Part-1”, thickness:20, count:2, svgArray:[svgf1, svgf2, …],{partTitle:”Part-2”, thickness:20, count:3, svgArray:[svgf3, svgf4, …]
    //stocksize is an array [width,height] of stock dimensions
    //we scale so the stock size takes up the %75 of screen
diff --git a/modules/index.js b/modules/index.js
index de206bcefec5f0905d64a60bf23743b7f898c5b9..ae2de366234a75e87e93b1671917e74fe9eb046c 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -4,8 +4,11 @@ module_menu('   in out','modules/character/in%20out')
 module_menu('   parse','modules/character/parse')
 module_menu('   variable','modules/character/variable')
 module_label('connect')
+module_label('   stl')
+module_menu('      STL','modules/connect/stl/STL')
 module_label('   svg')
 module_menu('      ExtractFaces','modules/connect/svg/ExtractFaces')
+module_menu('      Honeycomb','modules/connect/svg/Honeycomb')
 module_menu('      PCB','modules/connect/svg/PCB')
 module_menu('      SelectFace','modules/connect/svg/SelectFace')
 module_label('convert')
@@ -123,6 +126,8 @@ module_menu('      dxf','modules/path/formats/dxf')
 module_menu('      g-code','modules/path/formats/g-code')
 module_menu('      svg','modules/path/formats/svg')
 module_label('   machines')
+module_label('      laser cutter')
+module_menu('         Epilog','modules/path/machines/laser%20cutter/Epilog')
 module_label('      Roland')
 module_label('         milling')
 module_menu('            MDX-20','modules/path/machines/Roland/milling/MDX-20')
@@ -130,8 +135,6 @@ module_menu('            SRM-20','modules/path/machines/Roland/milli
 module_label('         vinyl cutter')
 module_menu('            GX-24','modules/path/machines/Roland/vinyl%20cutter/GX-24')
 module_menu('      ShopBot','modules/path/machines/ShopBot')
-module_label('      laser cutter')
-module_menu('         Epilog','modules/path/machines/laser%20cutter/Epilog')
 module_menu('   view','modules/path/view')
 module_label('processes')
 module_label('   cut')
diff --git a/programs/index.js b/programs/index.js
index 1032953164bf709de2755e73a0a7bd087bd69fe9..ffc42ed36af41f212d80c71cb2b16f93df0b90d9 100644
--- a/programs/index.js
+++ b/programs/index.js
@@ -17,8 +17,11 @@ program_menu('      cut svg','programs/machines/Epilog/cut%20svg')
 program_menu('      cut svg connect','programs/machines/Epilog/cut%20svg%20connect')
 program_label('   G-code')
 program_menu('      mill 2.5D stl','programs/machines/G-code/mill%202.5D%20stl')
+program_menu('      mill 2.5D stl connect','programs/machines/G-code/mill%202.5D%20stl%20connect')
 program_menu('      mill 2D png','programs/machines/G-code/mill%202D%20png')
 program_menu('      mill 2D svg','programs/machines/G-code/mill%202D%20svg')
+program_label('   RNDMC')
+program_menu('      honeycomb connect','programs/machines/RNDMC/honeycomb%20connect')
 program_label('   Roland')
 program_label('      mill')
 program_label('         MDX-20')
@@ -26,10 +29,11 @@ program_menu('            PCB','programs/machines/Roland/mill/MDX-20
 program_menu('            PCB png','programs/machines/Roland/mill/MDX-20/PCB%20png')
 program_menu('            PCB svg','programs/machines/Roland/mill/MDX-20/PCB%20svg')
 program_label('         SRM-20')
+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('            PCB png','programs/machines/Roland/mill/SRM-20/PCB%20png')
 program_menu('            PCB svg','programs/machines/Roland/mill/SRM-20/PCB%20svg')
 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_label('      vinyl cutter')
 program_label('         GX-24')
 program_menu('            cut png','programs/machines/Roland/vinyl%20cutter/GX-24/cut%20png')
@@ -37,6 +41,7 @@ program_menu('            cut svg','programs/machines/Roland/vinyl%2
 program_menu('            cut svg connect','programs/machines/Roland/vinyl%20cutter/GX-24/cut%20svg%20connect')
 program_label('   ShopBot')
 program_menu('      mill 2.5D stl','programs/machines/ShopBot/mill%202.5D%20stl')
+program_menu('      mill 2.5D stl connect','programs/machines/ShopBot/mill%202.5D%20stl%20connect')
 program_menu('      mill 2D png','programs/machines/ShopBot/mill%202D%20png')
 program_menu('      mill 2D png PCB','programs/machines/ShopBot/mill%202D%20png%20PCB')
 program_menu('      mill 2D stl','programs/machines/ShopBot/mill%202D%20stl')
diff --git a/programs/machines/G-code/mill 2.5D stl connect b/programs/machines/G-code/mill 2.5D stl connect
new file mode 100644
index 0000000000000000000000000000000000000000..92c0c26d63a1f0da9c9575fb35766f4467312c5a
--- /dev/null
+++ b/programs/machines/G-code/mill 2.5D stl connect	
@@ -0,0 +1 @@
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"458.79857101582286","left":"2975.883638471559","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"808.7985710158231","left":"3459.883638471559","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"902.7985710158231","left":"3013.883638471559","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = '0.5'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"359.7985710158231","left":"2559.883638471559","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"807.7985710158231","left":"2594.883638471559","inputs":{},"outputs":{}},"0.4793941661670936":{"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":"1056.1053724684725","left":"1458.7079375500448","inputs":{},"outputs":{}},"0.3040697193095865":{"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":"461.49148835504764","left":"988.5431668558716","inputs":{},"outputs":{}},"0.7667165137781767":{"definition":"//\n// offset\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// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = '25'\n   mod.distances = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         if ((mod.offset.value != '') && (mod.distances != ''))\n            offset()\n         else\n            mod.distances = ''\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"350.34902968661845","left":"3570.00249686672","inputs":{},"outputs":{}},"0.32304064019646705":{"definition":"//\n// mesh slice raster\n// \n// todo\n//    include slice plane triangles\n//    scale perturbation to resolution\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 slice raster'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '1'\n   mod.inunits.value = '0.03937007874015748'\n   mod.depth.value = '5'\n   mod.width.value = '1000'\n   mod.border.value = '0'\n   mod.delta = 1e-6\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_slice()}},\n   settings:{type:'',\n      event:function(evt){\n         for (var p in evt.detail)\n            if (p == 'depthmm') {\n               mod.depth.value = evt.detail[p]\n                  /parseFloat(mod.mmunits.value)\n               }\n         find_limits_slice()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)\n         }},\n   imageInfo:{type:'',\n      event:function(){\n         var obj = {}\n         obj.name = \"mesh slice raster\"\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         obj.dpi = mod.img.width/(mod.dx*parseFloat(mod.inunits.value))\n         mods.output(mod,'imageInfo',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen slice 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.slicecanvas = 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_slice()\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_slice()\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   // slice depth\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice Z depth: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.depth = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view slice\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 slice'))\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 slice\n//\nfunction find_limits_slice() {\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      slice_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border,delta:mod.delta})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var depth = evt.data.depth\n      var border = evt.data.border\n      var delta = evt.data.delta // perturb to remove degeneracies\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)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\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// slice mesh\n//   \nfunction slice_mesh() {\n   var blob = new Blob(['('+slice_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.slicecanvas.height*.5*(1-h/w)\n         var wd = mod.slicecanvas.width\n         var hd = mod.slicecanvas.width*h/w\n         }\n      else {\n         var x0 = mod.slicecanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.slicecanvas.height*w/h\n         var hd = mod.slicecanvas.height\n         }\n      var ctx = mod.slicecanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      })\n   var ctx = mod.slicecanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n   var depth = parseFloat(mod.depth.value)\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   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,depth:depth,\n      imgbuffer:img.data.buffer,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      delta:mod.delta},\n      [img.data.buffer])\n   }\nfunction slice_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var depth = evt.data.depth\n      var view = evt.data.mesh\n      var delta = evt.data.delta // perturb to remove degeneracies\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      //\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 slice image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] = 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      // find triangles crossing the slice\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)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         //\n         // assemble vertices\n         //\n         offset += 2\n         var v = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]\n         //\n         // sort z\n         //\n         v.sort(function(a,b) {\n            if (a[2] < b[2])\n               return -1\n            else if (a[2] > b[2])\n               return 1\n            else\n               return 0\n            })\n         //\n         // check for crossings\n         //\n         if ((v[0][2] < (zmax-depth)) && (v[2][2] > (zmax-depth))) {\n            //\n            //  crossing found, check for side and save\n            //\n            if (v[1][2] < (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[2][0]+(v[1][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               var y1 = v[2][1]+(v[1][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               }\n            else if (v[1][2] >= (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[1][0]+(v[0][0]-v[1][0])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               var y1 = v[1][1]+(v[0][1]-v[1][1])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               }\n            if (y0 < y1)\n               segs.push({x0:x0,y0:y0,x1:x1,y1:y1})\n            else\n               segs.push({x0:x1,y0:y1,x1:x0,y1:y0})\n            }\n         }\n      //\n      // fill interior\n      //\n      for (var row = 0; row < h; ++row) {\n         var y = ymin+(ymax-ymin)*row/(h-1)\n         rowsegs = segs.filter(p => ((p.y0 <= y) && (p.y1 >= y)))\n         var xs = rowsegs.map(p =>\n            (p.x0+(p.x1-p.x0)*(y-p.y0)/(p.y1-p.y0)))\n         xs.sort((a,b) => (a-b))\n         for (var col = 0; col < w; ++col) {\n            var x = xmin+(xmax-xmin)*col/(w-1)\n            var index = xs.findIndex((p) => (p >= x))\n            if (index == -1)\n               var i = 0\n            else\n               var i = 255*(index%2)\n            buf[(h-1-row)*w*4+col*4+0] = i\n            buf[(h-1-row)*w*4+col*4+1] = i\n            buf[(h-1-row)*w*4+col*4+2] = i\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // output the slice\n      //\n      self.postMessage({buffer:buf.buffer},[buf.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":"300.2962306652032","left":"1424.6209440086457","inputs":{},"outputs":{}},"0.4144526456371104":{"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":"1200.2152674081028","left":"1978.345635960845","inputs":{},"outputs":{}},"0.9325875387173613":{"definition":"//\n// mill raster 2.5D\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// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2.5D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.1'\n   mod.dia_mm.value = '2.54'\n   mod.cut_in.value = '0.1'\n   mod.cut_mm.value = '2.54'\n   mod.max_in.value = '1'\n   mod.max_mm.value = '25.4'\n   mod.number.value = '1'\n   mod.stepover.value = '0.5'\n   mod.merge.value = '1'\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            //\n            // calculation in progress, draw and accumulate\n            //\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value))\n               && (evt.detail.length > 0)) {\n               //\n               // layer detail present and offset not complete\n               //\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else if (mod.depthmm < parseFloat(mod.max_mm.value)) {\n               //\n               // layer loop not complete\n               //\n               merge_layer()\n               accumulate_toolpath()\n               clear_layer()\n               mod.depthmm += parseFloat(mod.cut_mm.value)\n               if (mod.depthmm > parseFloat(mod.max_mm.value)) {\n                  mod.depthmm = parseFloat(mod.max_mm.value)\n                  }\n               //\n               // clear offset\n               //\n               outputs.offset.event('')\n               //\n               // set new depth\n               //\n               outputs.depth.event(mod.depthmm)\n               //\n               // set new offset\n               //\n               mod.offsetCount = 0\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else {\n               //\n               // done, finish and output\n               //\n               draw_path(mod.toolpath)\n               draw_connections(mod.toolpath)\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   depth:{type:'',\n      event:function(depth){\n         mods.output(mod,'depth',{depthmm:depth})\n         }\n      },\n   diameter:{type:'',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'',\n      event:function(size){\n         mods.output(mod,'offset',size)\n         }\n      },\n   toolpath:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.toolpath\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         outputs.offset.event('') // clear offset\n         mod.depthmm = parseFloat(mod.cut_mm.value)\n         outputs.depth.event(mod.depthmm) // set depth\n         mod.toolpath = [] // start new toolpath\n         clear_layer() // clear layer\n         outputs.diameter.event()\n         outputs.offset.event( // set offset\n            mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_layer\n//\nfunction clear_layer() {\n   mod.path = []\n   mod.offset = 0.5\n   mod.offsetCount = 0\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute(\n      'viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_layer\n//\nfunction merge_layer() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// accumulate_toolpath\n//\nfunction accumulate_toolpath() {\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var newseg = []\n      for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n         var idepth = -Math.round(mod.dpi*mod.depthmm/25.4)\n         var point = mod.path[seg][pt].concat(idepth)\n         newseg.splice(newseg.length,0,point)\n         }\n      mod.toolpath.push(newseg)\n      }\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS(\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            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS(\n                  'http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute(\n                  'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS(\n         'http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = path[segment-1][path[segment-1].length-1][0]\n      var y1 = h-path[segment-1][path[segment-1].length-1][1]-1\n      var x2 = path[segment][0][0]\n      var y2 = h-path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS(\n            'http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute(\n            'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"439.00932025295833","left":"1982.700932035969","inputs":{},"outputs":{}},"0.7335721197356367":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '20'\n   mod.plungespeed.value = '20'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '5'\n   mod.spindlespeed.value = '10000'\n   mod.tool.value = '1'\n   mod.coolanton.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // Inch or mm\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_speed = parseFloat(mod.jogspeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_speed /= 25.4\n      jog_height /= 25.4\n   }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n   str += \"G04 P1\\n\" // give spindle 1 second to spin up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"1054.8057495453727","left":"962.2547424183412","inputs":{},"outputs":{}},"0.9327347189729929":{"definition":"//\r\n// STL module extracts stl from Tools/FabLab Connect command of SolidWorks products\r\n// \r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2019\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'STL connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function () {\r\n    mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n    mod.port = getParameterByName('swPort') || '80'\r\n    mod.socket = 0\r\n   mod.sag.value = '0.1'\n   mod.angle.value = '45'\n    socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   }\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   mesh:{type:'STL',\r\n      event:function(buffer){\r\n         mods.output(mod,'mesh',buffer)}}\r\n      }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // canvas\r\n   //\r\n   var canvas = document.createElement('canvas')\r\n      canvas.width = mods.ui.canvas\r\n      canvas.height = mods.ui.canvas\r\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\r\n      div.appendChild(canvas)\r\n      mod.canvas = canvas\r\n      div.appendChild(document.createElement('br'))\r\n\r\n      div.appendChild(document.createTextNode('server:'))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 12\r\n      div.appendChild(input)\r\n      mod.status = input\r\n      div.appendChild(document.createElement('br'))\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click', function () {\r\n          socket_open()\r\n      })\r\n      div.appendChild(btn)\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click', function () {\r\n          socket_close()\r\n      })\r\n      div.appendChild(btn)\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('sag: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.sag = input\r\n      div.appendChild(document.createTextNode('(mm)'))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('angle: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.angle = input\r\n      div.appendChild(document.createTextNode('(degree)'))\r\n      div.appendChild(document.createElement('br'))\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Extract STL'))\r\n      btn.addEventListener('click', function () {\r\n          extract_STL()\r\n      })\r\n      div.appendChild(btn)\r\n\r\n   //\r\n   // info\r\n   //\r\n   var info = document.createElement('div')\r\n   info.setAttribute('id', div.id + 'info')\r\n       var text = document.createTextNode('name: ')\n       info.appendChild(text)\n       mod.name = text\r\n       info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('size: ')\r\n         info.appendChild(text)\r\n         mod.sizen = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('triangles: ')\r\n         info.appendChild(text)\r\n         mod.trianglesn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dx: ')\r\n         info.appendChild(text)\r\n         mod.dxn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dy: ')\r\n         info.appendChild(text)\r\n         mod.dyn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dz: ')\r\n         info.appendChild(text)\r\n         mod.dzn = text\r\n         div.appendChild(info)\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\n    //\r\n    // local functions\r\n    //\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href;\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url);\r\n    if (!results) return null;\r\n    if (!results[2]) return '';\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\r\n}\r\n\r\nfunction socket_open() {\r\n    var url = \"ws://\" + mod.address + ':' + mod.port\r\n    mod.socket = new WebSocket(url)\r\n    mod.socket.onopen = function (event) {\r\n        mod.status.value = \"opened\"\r\n        var connect = {}\r\n        connect.modCmd = 'connect'\r\n        connect.owner = getParameterByName('swOwner')\r\n        connect.id = getParameterByName('swID')\r\n        socket_send(JSON.stringify(connect))\r\n    }\r\n    mod.socket.onerror = function (event) {\r\n        mod.status.value = \"can not open\"\r\n    }\r\n    mod.socket.onmessage = function (event) {\r\n        mod.status.value = \"receive\"\r\n        var swData = JSON.parse(event.data)\r\n        if (swData.swType === \"AutoExtractSTL\") {\r\n            var partName = swData.data.partName\r\n            if (partName.length > 25)\r\n                partName = partName.slice(0, 22) +'...'\r\n            mod.name.nodeValue = \"name: \" + partName\r\n            mod.str = swData.data.stl\r\n            stl_load_handler()\r\n        }\r\n    }\r\n    mod.socket.onclose = function (event) {\r\n        mod.status.value = \"connection closed\"\r\n    }\r\n}\r\nfunction socket_close() {\r\n    mod.socket.close()\r\n    mod.status.value = \"closed\"\r\n    mod.socket = 0\r\n}\r\nfunction socket_send(msg) {\r\n    if (mod.socket != 0) {\r\n        mod.status.value = \"send\"\r\n        mod.socket.send(msg)\r\n    }\r\n    else {\r\n        mod.status.value = \"can't send, not open\"\r\n    }\r\n}\r\nfunction extract_STL() {\r\n    var modcmd = new Object;\r\n    modcmd.modCmd = \"AutoExtractSTL\";\r\n    modcmd.sag = Number(mod.sag.value); // mm\r\n    modcmd.angle = Number(mod.angle.value); //degree\r\n    socket_send(JSON.stringify(modcmd))\r\n}\r\n\r\nfunction base64ToArrayBuffer(base64) {\r\n    var binary_string = window.atob(base64);\r\n    var len = binary_string.length;\r\n    var bytes = new Uint8Array(len);\r\n    for (var i = 0; i < len; i++) {\r\n        bytes[i] = binary_string.charCodeAt(i);\r\n    }\r\n    return bytes.buffer;\r\n}\r\n\r\n//\r\n// load handler\r\n//\r\nfunction stl_load_handler() {\r\n   //\r\n   // check for binary STL\r\n    //\r\n   var arraybuf = base64ToArrayBuffer(mod.str)\r\n   var endian = true\r\n   var view = new DataView(arraybuf)\r\n   var triangles = view.getUint32(80, endian)\r\n   var size = 80+4+triangles*(4*12+2)\r\n   if (size != view.byteLength) {\r\n      mod.sizen.nodeValue = 'error: not binary STL'\r\n      mod.trianglesn.nodeValue = ''\r\n      mod.dxn.nodeValue = ''\r\n      mod.dyn.nodeValue = ''\r\n      mod.dzn.nodeValue = ''\r\n      return\r\n      }\r\n   mod.sizen.nodeValue = 'size: '+size\r\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\r\n   //\r\n   // find limits and draw\r\n   //\r\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\r\n   var url = window.URL.createObjectURL(blob)\r\n   var webworker = new Worker(url)\r\n   webworker.addEventListener('message',function(evt) {\r\n      //\r\n      // worker response\r\n      //\r\n      window.URL.revokeObjectURL(url)\r\n      //\r\n      // size\r\n      //\r\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\r\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\r\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\r\n      //\r\n      // image\r\n      //\r\n      var image = evt.data.image\r\n      var height = mod.canvas.height\r\n      var width = mod.canvas.width\r\n      var buffer = new Uint8ClampedArray(evt.data.image)\r\n      var imgdata = new ImageData(buffer,width,height)\r\n      var ctx = mod.canvas.getContext(\"2d\")\r\n      ctx.putImageData(imgdata,0,0)\r\n      //\r\n      // output\r\n      //\r\n      outputs.mesh.event(evt.data.mesh)\r\n      })\r\n   var ctx = mod.canvas.getContext(\"2d\")\r\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\r\n   var img = ctx.getImageData(0, 0, mod.canvas.width, mod.canvas.height)\r\n   //\r\n   // call worker\r\n   //\r\n   webworker.postMessage({\r\n      height:mod.canvas.height,width:mod.canvas.width,\r\n      image: img.data.buffer, mesh: arraybuf},\r\n      [img.data.buffer, arraybuf])\r\n   }\r\nfunction draw_limits_worker() {\r\n   self.addEventListener('message',function(evt) {\r\n      //\r\n      // function to draw line\r\n      //\r\n      function line(x0,y0,x1,y1) {\r\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\r\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\r\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\r\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\r\n         var row,col\r\n         var idx = ix1-ix0\r\n         var idy = iy1-iy0\r\n         if (Math.abs(idy) > Math.abs(idx)) {\r\n            (idy > 0) ?\r\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\r\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\r\n            for (row = row0; row <= row1; ++row) {\r\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\r\n               image[row*width*4+col*4+0] = 0\r\n               image[row*width*4+col*4+1] = 0\r\n               image[row*width*4+col*4+2] = 0\r\n               image[row*width*4+col*4+3] = 255\r\n               }\r\n            }\r\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\r\n            (idx > 0) ?\r\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\r\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\r\n            for (col = col0; col <= col1; ++col) {\r\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\r\n               image[row*width*4+col*4+0] = 0\r\n               image[row*width*4+col*4+1] = 0\r\n               image[row*width*4+col*4+2] = 0\r\n               image[row*width*4+col*4+3] = 255\r\n               }\r\n            }\r\n         else {\r\n            row = iy0\r\n            col = ix0\r\n            image[row*width*4+col*4+0] = 0\r\n            image[row*width*4+col*4+1] = 0\r\n            image[row*width*4+col*4+2] = 0\r\n            image[row*width*4+col*4+3] = 255\r\n            }\r\n         }\r\n      //\r\n      // get variables\r\n      //\r\n      var height = evt.data.height\r\n      var width = evt.data.width\r\n      var endian = true\r\n      var image = new Uint8ClampedArray(evt.data.image)\r\n      var view = new DataView(evt.data.mesh)\r\n      var triangles = view.getUint32(80,endian)\r\n      //\r\n      // find limits\r\n      //\r\n      var offset = 80+4\r\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\r\n      var xmin = Number.MAX_VALUE\r\n      var xmax = -Number.MAX_VALUE\r\n      var ymin = Number.MAX_VALUE\r\n      var ymax = -Number.MAX_VALUE\r\n      var zmin = Number.MAX_VALUE\r\n      var zmax = -Number.MAX_VALUE\r\n      for (var t = 0; t < triangles; ++t) {\r\n         offset += 3*4\r\n         x0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x0 > xmax) xmax = x0\r\n         if (x0 < xmin) xmin = x0\r\n         y0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y0 > ymax) ymax = y0\r\n         if (y0 < ymin) ymin = y0\r\n         z0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z0 > zmax) zmax = z0\r\n         if (z0 < zmin) zmin = z0\r\n         x1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x1 > xmax) xmax = x1\r\n         if (x1 < xmin) xmin = x1\r\n         y1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y1 > ymax) ymax = y1\r\n         if (y1 < ymin) ymin = y1\r\n         z1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z1 > zmax) zmax = z1\r\n         if (z1 < zmin) zmin = z1\r\n         x2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x2 > xmax) xmax = x2\r\n         if (x2 < xmin) xmin = x2\r\n         y2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y2 > ymax) ymax = y2\r\n         if (y2 < ymin) ymin = y2\r\n         z2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z2 > zmax) zmax = z2\r\n         if (z2 < zmin) zmin = z2\r\n         offset += 2\r\n         }\r\n      var dx = xmax-xmin\r\n      var dy = ymax-ymin\r\n      var dz = zmax-zmin\r\n      //\r\n      // draw mesh\r\n      //\r\n      if (dx > dy) {\r\n         var xo = 0\r\n         var yo = height*.5*(1-dy/dx)\r\n         var xw = width-1\r\n         var yh = (width-1)*dy/dx\r\n         }\r\n      else {\r\n         var xo = width*.5*(1-dx/dy)\r\n         var yo = 0\r\n         var xw = (height-1)*dx/dy\r\n         var yh = height-1\r\n         }\r\n      offset = 80+4\r\n      for (var t = 0; t < triangles; ++t) {\r\n         offset += 3*4\r\n         x0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         x1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         x2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         offset += 2\r\n         line(x0,y0,x1,y1)\r\n         line(x1,y1,x2,y2)\r\n         line(x2,y2,x0,y0)\r\n         }\r\n      //\r\n      // return results and close\r\n      //\r\n      self.postMessage({\r\n         dx:dx,dy:dy,dz:dz,\r\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\r\n      self.close()\r\n      })\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"239.08229921859862","left":"541.1152264935706","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"depth\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4144526456371104\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7335721197356367\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7335721197356367\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9327347189729929\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}"]}
\ No newline at end of file
diff --git a/programs/machines/RNDMC/honeycomb connect b/programs/machines/RNDMC/honeycomb connect
new file mode 100644
index 0000000000000000000000000000000000000000..d7dbba86b932ff45382f26fa7e9c503d1c0392c9
--- /dev/null
+++ b/programs/machines/RNDMC/honeycomb connect	
@@ -0,0 +1 @@
+{"modules":{"0.22479242263560417":{"definition":"//\r\n// RNDMC server module\r\n//\r\n// Neil Gershenfeld \r\n// (c) Massachusetts Institute of Technology 2017\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'WebSocket RNDMC'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address.value = '127.0.0.1'\n   mod.port.value = '1234'\n   mod.socket = null\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   path:{type:'array',\r\n       event: function (evt) {\r\n           mod.path = evt.detail\r\n           mod.label.nodeValue = 'send path to RNDMC'\r\n           console.log(JSON.stringify(mod.path))\r\n        }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // server\r\n   //\r\n   div.appendChild(document.createTextNode('address: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.address = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.port = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         socket_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         socket_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.padding = mods.ui.padding\r\n   btn.style.margin = 1\r\n   var span = document.createElement('span')\r\n   var text = document.createTextNode('waiting for file')\r\n   mod.label = text\r\n   span.appendChild(text)\r\n   mod.labelspan = span\r\n   btn.appendChild(span)\r\n   btn.addEventListener('click', function () {\r\n       if (mod.socket == null) {\r\n           mod.status.value = \"can't send, not open\"\r\n       }\r\n       else if (mod.label.nodeValue == 'send path to RNDMC') {\r\n           socket_send(JSON.stringify(mod.path))\r\n           mod.label.nodeValue = 'cancel'\r\n       }\r\n       else if (mod.label.nodeValue == 'cancel') {\r\n           socket_send('cancel')\r\n       }\r\n   })\r\n   div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n      mod.status.value = \"socket opened\"\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open socket\"\r\n      mod.socket = null\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = event.data\r\n      if ((event.data == 'done') || (event.data == 'cancel')\r\n         || (event.data.slice(0,5) == 'error')) {\r\n         mod.label.nodeValue = 'waiting for path'\r\n         mod.labelspan.style.fontWeight = 'normal'\r\n         }\r\n      }\r\n   }\r\nfunction socket_close() {\r\n    if (mod.socket) {\r\n        mod.socket.close()\r\n        mod.status.value = \"socket closed\"\r\n        mod.socket = null\r\n    }\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != null) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"348.87777325850277","left":"755.4800755846891","inputs":{},"outputs":{}},"0.7272977112269394":{"definition":"//\r\n// convert honeycomb SVG\r\n//\r\n// Neil Gershenfeld \r\n// (c) Massachusetts Institute of Technology 2016\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function () {\r\n    //\r\n    // module globals\r\n    //\r\n    var mod = {}\r\n    //\r\n    // name\r\n    //\r\n    var name = 'convert honeycomb SVG'\r\n    //\r\n    // initialization\r\n    //\r\n    var init = function () {\r\n\r\n    }\r\n    //\r\n    // inputs\r\n    //\r\n    var inputs = {\r\n        SVG: {\r\n            type: 'string',\r\n            event: function (evt) {\r\n                mod.svg = evt.detail\r\n                mod.colorpaths = undefined;\r\n                get_size()\r\n                vectorize()\r\n            }\r\n        }\r\n    }\r\n    //\r\n    // outputs\r\n    //\r\n    var outputs = {\r\n        path:{type:'array',\r\n            event: function () {\r\n                mods.output(mod, 'path', mod.path)\r\n            }}\r\n    }\r\n    //\r\n    // interface\r\n    //\r\n    var interface = function (div) {\r\n        mod.div = div\r\n        //\r\n        // on-screen SVG\r\n        //\r\n        var svgNS = \"http://www.w3.org/2000/svg\"\r\n        var svg = document.createElementNS(svgNS, \"svg\")\r\n        svg.setAttribute('id', mod.div.id + 'svg')\r\n        svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\r\n           \"xmlns:xlink\", \"http://www.w3.org/1999/xlink\")\r\n        svg.setAttribute('width', mods.ui.canvas)\r\n        svg.setAttribute('height', mods.ui.canvas)\r\n        svg.style.backgroundColor = 'rgb(255,255,255)'\r\n        var g = document.createElementNS(svgNS, 'g')\r\n        g.setAttribute('id', mod.div.id + 'g')\r\n        svg.appendChild(g)\r\n        div.appendChild(svg)\r\n        div.appendChild(document.createElement('br'))\r\n        //\r\n        // off-screen image canvas\r\n        //\r\n        var canvas = document.createElement('canvas')\r\n        mod.img = canvas\r\n        //\r\n        // colors\r\n        //\r\n        div.appendChild(document.createTextNode('select path:'))\r\n        div.appendChild(document.createElement('br'))\r\n        var select = document.createElement('select')\r\n        select.setAttribute('style', 'width:150px');\r\n        var el1 = document.createElement('option')\r\n        el1.textContent = 'cut'\r\n        el1.value = 'red'\r\n        select.appendChild(el1)\r\n        var el2 = document.createElement('option')\r\n        el2.textContent = 'fold mountain'\r\n        el2.value = 'green'\r\n        select.appendChild(el2)\r\n        var el3 = document.createElement('option')\r\n        el3.textContent = 'fold valley'\r\n        el3.value = 'blue'\r\n        select.appendChild(el3)\r\n        select.addEventListener(\"change\", function () {\r\n            vectorize()\r\n        });\r\n        div.appendChild(select)\r\n        mod.selectPaths = select\r\n        div.appendChild(document.createElement('br'))\r\n        //\r\n        // view button\r\n        //\r\n        var btn = document.createElement('button')\r\n        btn.style.padding = mods.ui.padding\r\n        btn.style.margin = 1\r\n        btn.appendChild(document.createTextNode('view'))\r\n        btn.addEventListener('click', function () {\r\n            var win = window.open('')\r\n            var btn = document.createElement('button')\r\n            btn.appendChild(document.createTextNode('close'))\r\n            btn.style.padding = mods.ui.padding\r\n            btn.style.margin = 1\r\n            btn.addEventListener('click', function () {\r\n                win.close()\r\n            })\r\n            win.document.body.appendChild(btn)\r\n            win.document.body.appendChild(document.createElement('br'))\r\n            var svg = document.getElementById(mod.div.id + 'svg')\r\n            var clone = svg.cloneNode(true)\r\n            clone.setAttribute('width', mod.width)\r\n            clone.setAttribute('height', mod.height)\r\n            win.document.body.appendChild(clone)\r\n        })\r\n        div.appendChild(btn)\r\n    }\r\n    //\r\n    // local functions\r\n    //\r\n    // get size\r\n    //\r\n    function get_size() {\r\n        var i = mod.svg.indexOf(\"width\")\r\n        if (i == -1) {\r\n            var width = 1\r\n            var height = 1\r\n            var units = 90\r\n        }\r\n        else {\r\n            var i1 = mod.svg.indexOf(\"\\\"\", i + 1)\r\n            var i2 = mod.svg.indexOf(\"\\\"\", i1 + 1)\r\n            var width = mod.svg.substring(i1 + 1, i2)\r\n            i = mod.svg.indexOf(\"height\")\r\n            i1 = mod.svg.indexOf(\"\\\"\", i + 1)\r\n            i2 = mod.svg.indexOf(\"\\\"\", i1 + 1)\r\n            var height = mod.svg.substring(i1 + 1, i2)\r\n            ih = mod.svg.indexOf(\"height\")\r\n            if (width.indexOf(\"px\") != -1) {\r\n                width = width.slice(0, -2)\r\n                height = height.slice(0, -2)\r\n                var units = 90\r\n            }\r\n            else if (width.indexOf(\"mm\") != -1) {\r\n                width = width.slice(0, -2)\r\n                height = height.slice(0, -2)\r\n                var units = 25.4\r\n            }\r\n            else if (width.indexOf(\"cm\") != -1) {\r\n                width = width.slice(0, -2)\r\n                height = height.slice(0, -2)\r\n                var units = 2.54\r\n            }\r\n            else if (width.indexOf(\"in\") != -1) {\r\n                width = width.slice(0, -2)\r\n                height = height.slice(0, -2)\r\n                var units = 1\r\n            }\r\n            else {\r\n                var units = 90\r\n            }\r\n        }\r\n        mod.width = Math.round(parseFloat(width))\r\n        mod.height = Math.round(parseFloat(height))\r\n        mod.units = units\r\n    }\r\n    //\r\n    // local functions\r\n    //\r\n    // vectorize\r\n    //\r\n    function vectorize() {\r\n\r\n        svg_to_path();\r\n        mod.path = mod.colorpaths[mod.selectPaths.value]\r\n        draw_path(mod.path);\r\n        outputs.path.event(mod.path)\r\n    }\r\n    //\r\n    // draw path\r\n    //\r\n    function draw_path(path) {\r\n        var svg = document.getElementById(mod.div.id + 'svg')\r\n        svg.setAttribute('viewBox', \"0 0 \" + (mod.width *1.1- 1) + \" \" + (mod.height- 1))\r\n        var g = document.getElementById(mod.div.id + 'g')\r\n        svg.removeChild(g)\r\n        var g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\r\n        g.setAttribute('id', mod.div.id + 'g')\r\n        var h = mod.height\r\n        var w = mod.width\r\n        var xend = null\r\n        var yend = null\r\n        //\r\n        // loop over segments\r\n        //\r\n        for (var segment in path) {\r\n            if (path[segment].length > 1) {\r\n                if (xend != null) {\r\n                    //\r\n                    // draw connection from previous segment\r\n                    //\r\n                    var line = document.createElementNS('http://www.w3.org/2000/svg', 'line')\r\n                    line.setAttribute('stroke', 'red')\r\n                    line.setAttribute('stroke-width', 1)\r\n                    line.setAttribute('stroke-linecap', 'round')\r\n                    var x1 = xend\r\n                    var y1 = yend\r\n                    var x2 = path[segment][0][0]\r\n                    var y2 = h - path[segment][0][1] - 1\r\n                    line.setAttribute('x1', x1)\r\n                    line.setAttribute('y1', y1)\r\n                    line.setAttribute('x2', x2)\r\n                    line.setAttribute('y2', y2)\r\n                    var dx = x2 - x1\r\n                    var dy = y2 - y1\r\n                    var d = Math.sqrt(dx * dx + dy * dy)\r\n                    if (d > 0) {\r\n                        nx = 6 * dx / d\r\n                        ny = 6 * dy / d\r\n                        var tx = 3 * dy / d\r\n                        var ty = -3 * dx / d\r\n                        g.appendChild(line)\r\n                        triangle = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\r\n                        triangle.setAttribute('points', x2 + ',' + y2 + ' ' + (x2 - nx + tx) + ',' + (y2 - ny + ty)\r\n                           + ' ' + (x2 - nx - tx) + ',' + (y2 - ny - ty))\r\n                        triangle.setAttribute('fill', 'red')\r\n                        g.appendChild(triangle)\r\n                    }\r\n                }\r\n                //\r\n                // loop over points\r\n                //\r\n                for (var point = 1; point < path[segment].length; ++point) {\r\n                    var line = document.createElementNS('http://www.w3.org/2000/svg', 'line')\r\n                    line.setAttribute('stroke', 'black')\r\n                    line.setAttribute('stroke-width', 1)\r\n                    line.setAttribute('stroke-linecap', 'round')\r\n                    var x1 = path[segment][point - 1][0]\r\n                    var y1 = h - path[segment][point - 1][1] - 1\r\n                    var x2 = path[segment][point][0]\r\n                    var y2 = h - path[segment][point][1] - 1\r\n                    xend = x2\r\n                    yend = y2\r\n                    line.setAttribute('x1', x1)\r\n                    line.setAttribute('y1', y1)\r\n                    line.setAttribute('x2', x2)\r\n                    line.setAttribute('y2', y2)\r\n                    var dx = x2 - x1\r\n                    var dy = y2 - y1\r\n                    var d = Math.sqrt(dx * dx + dy * dy)\r\n                    if (d > 0) {\r\n                        nx = 6 * dx / d\r\n                        ny = 6 * dy / d\r\n                        var tx = 3 * dy / d\r\n                        var ty = -3 * dx / d\r\n                        g.appendChild(line)\r\n                        triangle = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\r\n                        triangle.setAttribute('points', x2 + ',' + y2 + ' ' + (x2 - nx + tx) + ',' + (y2 - ny + ty)\r\n                           + ' ' + (x2 - nx - tx) + ',' + (y2 - ny - ty))\r\n                        triangle.setAttribute('fill', 'black')\r\n                        g.appendChild(triangle)\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        svg.appendChild(g)\r\n    }\r\n    //\r\n    // convert honeycomb svg to path\r\n    //\r\n    function svg_to_path() {\r\n        var svg = mod.svg\r\n        if (mod.colorpaths) {\r\n            return\r\n        }\r\n        var colorpaths = { 'red': [], 'blue': [], 'green': [] }\r\n        var parser = new DOMParser();\r\n        var doc = parser.parseFromString(svg, \"application/xml\");\r\n        var nextElm = doc.children[0].firstElementChild;\r\n        var colorpaths = { 'red': [], 'blue': [], 'green': [] };\r\n        var valueToInt = function (val) {\r\n            var mmToPixel = 3.7795275591;\r\n            return Math.round(parseFloat(val) *mmToPixel)\r\n        }\r\n        var sameCoord = function (coord1, coord2) {\r\n            return coord1[0] === coord2[0] && coord1[1] === coord2[1];\r\n        }\r\n\r\n        while (nextElm) {\r\n            var color;\r\n            var styles = nextElm.getAttribute('style').split(';');\r\n            for (stylei in styles) {\r\n                var style = styles[stylei].split(':')\r\n                if (style[0] === 'stroke') {\r\n                    color = style[1];\r\n                }\r\n            }\r\n\r\n            paths = colorpaths[color];\r\n            if (nextElm.tagName === 'line') {\r\n                var x1 = valueToInt(nextElm.getAttribute('x1'));\r\n                var y1 = mod.height - valueToInt(nextElm.getAttribute('y1'));\r\n                var x2 = valueToInt(nextElm.getAttribute('x2'));\r\n                var y2 = mod.height - valueToInt(nextElm.getAttribute('y2'));\r\n                var appended = false;\r\n                for (pathi in paths) {\r\n                    var path = paths[pathi];\r\n                    if (sameCoord(path[path.length - 1], [x1, y1])) {\r\n                        path.push([x2, y2]);\r\n                        appended = true;\r\n                    }\r\n                }\r\n                if (!appended) {\r\n                    paths.push([[x1, y1], [x2, y2]])\r\n                }\r\n            }\r\n            else if (nextElm.tagName === 'polyline') {\r\n                var points = [];\r\n                var pts = nextElm.getAttribute('points').split(' ');\r\n                for (pti in pts) {\r\n                    var coords = pts[pti].split(',')\r\n                    points.push([valueToInt(coords[0]), mod.height - valueToInt(coords[1])]);\r\n                }\r\n                paths.push(points);\r\n            }\r\n\r\n            nextElm = nextElm.nextElementSibling;\r\n        }\r\n            \r\n        mod.colorpaths = colorpaths;\r\n    }\r\n    //\r\n    // return values\r\n    //\r\n    return ({\r\n        mod: mod,\r\n        name: name,\r\n        init: init,\r\n        inputs: inputs,\r\n        outputs: outputs,\r\n        interface: interface\r\n    })\r\n}())\r\n","top":"185.78584481355912","left":"379.4665171150598","inputs":{},"outputs":{}},"0.29776381938511776":{"definition":"//\r\n// Honeycomb module extracts flatten honeycomb sheet from Tools/FabLab Connect command of SolidWorks products\r\n// \r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2019\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'Honeycomb connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n   mod.port = getParameterByName('swPort') || '80'\r\n   mod.socket = 0\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n    SVG: {\r\n        type: 'string',\r\n        event: function () {\r\n            mods.output(mod, 'SVG', mod.str)\r\n        }\r\n    },\r\n    file: {\r\n        type: 'object',\r\n        event: function (str) {\r\n            obj = {}\r\n            obj.name = mod.partName + \".svg\"\r\n            obj.contents = str\r\n            mods.output(mod, 'file', obj)\r\n        }\r\n    }\r\n}\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n\r\n    // on-screen drawing canvas\r\n    //\r\n    var canvas = document.createElement('canvas')\r\n    canvas.width = mods.ui.canvas\r\n    canvas.height = mods.ui.canvas\r\n    canvas.style.backgroundColor = 'rgb(255,255,255)'\r\n    div.appendChild(canvas)\r\n    mod.canvas = canvas\r\n    div.appendChild(document.createElement('br'))\r\n    //\r\n    // off-screen image canvas\r\n    //\r\n    var canvas = document.createElement('canvas')\r\n    mod.img = canvas\r\n\r\n   div.appendChild(document.createTextNode('server:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n   input.type = 'text'\r\n   input.size = 12\r\n   div.appendChild(input)\r\n   mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('open'))\r\n   btn.addEventListener('click', function () {\r\n       socket_open()\r\n   })\r\n   div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('close'))\r\n   btn.addEventListener('click', function () {\r\n       socket_close()\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('Extract SVG'))\r\n   btn.addEventListener('click', function () {\r\n       extract_SVG()\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n    //\r\n    // view button\r\n    //\r\n   var btn = document.createElement('button')\r\n   btn.style.padding = mods.ui.padding\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('view'))\r\n   btn.addEventListener('click', function () {\r\n       var win = window.open('')\r\n       var btn = document.createElement('button')\r\n       btn.appendChild(document.createTextNode('close'))\r\n       btn.style.padding = mods.ui.padding\r\n       btn.style.margin = 1\r\n       btn.addEventListener('click', function () {\r\n           win.close()\r\n       })\r\n       win.document.body.appendChild(btn)\r\n       win.document.body.appendChild(document.createElement('br'))\r\n       var canvas = document.createElement('canvas')\r\n       canvas.width = mod.img.width\r\n       canvas.height = mod.img.height\r\n       win.document.body.appendChild(canvas)\r\n       var ctx = canvas.getContext(\"2d\")\r\n       ctx.drawImage(mod.img, 0, 0)\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n\r\n    //\r\n    // info div\r\n    //\r\n   var info = document.createElement('div')\r\n   info.setAttribute('id', div.id + 'info')\r\n   mod.name = document.createTextNode('name:')\r\n   info.appendChild(mod.name)\r\n   mod.thickness = document.createTextNode('thickness: ')\r\n   div.appendChild(mod.thickness)\r\n   info.appendChild(document.createElement('br'))\r\n   mod.width = document.createTextNode('width:')\r\n   info.appendChild(mod.width)\r\n   info.appendChild(document.createElement('br'))\r\n   mod.height = document.createTextNode('height:')\r\n   info.appendChild(mod.height)\r\n   div.appendChild(info)\r\n\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href;\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url);\r\n    if (!results) return null;\r\n    if (!results[2]) return '';\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\r\n}\r\n\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address+':'+mod.port\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n       mod.status.value = \"opened\"\r\n       var connect = {}\r\n       connect.modCmd = 'connect'\r\n       connect.owner = getParameterByName('swOwner')\r\n       connect.id = getParameterByName('swID')\r\n       socket_send(JSON.stringify(connect))\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = \"receive\"\r\n      var swData = JSON.parse(event.data);\r\n      if (swData.swType === \"HoneycombFlattenSVG\") {\r\n          mod.partName = swData.data.partName\r\n          var partName = swData.data.partName\r\n          if (partName.length > 25)\r\n              partName = partName.slice(0, 22) + '...'\r\n          mod.name.nodeValue = \"name: \" + partName\r\n          mod.thickness.nodeValue = \"thickness: \" + swData.data.thickness + ' (mm)';\r\n          mod.str = swData.data.svg\r\n          svg_load_handler()\r\n          outputs.SVG.event(mod.str)\r\n          outputs.file.event(mod.str)\r\n      }\r\n   }\r\n   mod.socket.onclose = function (event) {\r\n       mod.status.value = \"connection closed\"\r\n   }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"closed\"\r\n   mod.socket = 0\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != 0) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction extract_SVG() {\r\n   var modcmd = new Object;\r\n   modcmd.modCmd = \"AutoHoneycombFlatten\";\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\n\r\n    //\r\n    // load handler\r\n    //\r\nfunction svg_load_handler() {\r\n    //\r\n    // parse size\r\n    //\r\n    var i = mod.str.indexOf(\"width\")\r\n    if (i == -1) {\r\n        mod.width.nodeValue = \"width: not found\"\r\n        mod.height.nodeValue = \"height: not found\"\r\n    }\r\n    else {\r\n        var i1 = mod.str.indexOf(\"\\\"\", i + 1)\r\n        var i2 = mod.str.indexOf(\"\\\"\", i1 + 1)\r\n        var width = mod.str.substring(i1 + 1, i2)\r\n        i = mod.str.indexOf(\"height\")\r\n        i1 = mod.str.indexOf(\"\\\"\", i + 1)\r\n        i2 = mod.str.indexOf(\"\\\"\", i1 + 1)\r\n        var height = mod.str.substring(i1 + 1, i2)\r\n        ih = mod.str.indexOf(\"height\")\r\n        if (width.indexOf(\"px\") != -1) {\r\n            width = width.slice(0, -2)\r\n            height = height.slice(0, -2)\r\n            var units = 90\r\n        }\r\n        else if (width.indexOf(\"mm\") != -1) {\r\n            width = width.slice(0, -2)\r\n            height = height.slice(0, -2)\r\n            var units = 25.4\r\n        }\r\n        else if (width.indexOf(\"cm\") != -1) {\r\n            width = width.slice(0, -2)\r\n            height = height.slice(0, -2)\r\n            var units = 2.54\r\n        }\r\n        else if (width.indexOf(\"in\") != -1) {\r\n            width = width.slice(0, -2)\r\n            height = height.slice(0, -2)\r\n            var units = 1\r\n        }\r\n        else {\r\n            var units = 90\r\n        }\r\n        mod.width.nodeValue = \"width: \" + width / 3.543307 + ' (mm)';\r\n        mod.height.nodeValue = \"height: \" + height / 3.543307 + ' (mm)';\r\n    }\r\n    //\r\n    // display\r\n    //\r\n    var img = new Image()\r\n    var src = \"data:image/svg+xml;base64,\" + window.btoa(mod.str)\r\n    img.setAttribute(\"src\", src)\r\n    img.onload = function () {\r\n        if (img.width > img.height) {\r\n            var x0 = 0\r\n            var y0 = mod.canvas.height * .5 * (1 - img.height / img.width)\r\n            var w = mod.canvas.width\r\n            var h = mod.canvas.width * img.height / img.width\r\n        }\r\n        else {\r\n            var x0 = mod.canvas.width * .5 * (1 - img.width / img.height)\r\n            var y0 = 0\r\n            var w = mod.canvas.height * img.width / img.height\r\n            var h = mod.canvas.height\r\n        }\r\n        var ctx = mod.canvas.getContext(\"2d\")\r\n        ctx.clearRect(0, 0, mod.canvas.width, mod.canvas.height)\r\n        ctx.drawImage(img, x0, y0, w, h)\r\n        var ctx = mod.img.getContext(\"2d\")\r\n        ctx.canvas.width = img.width\r\n        ctx.canvas.height = img.height\r\n        ctx.drawImage(img, 0, 0)\r\n        outputs.SVG.event()\r\n    }\r\n}\r\n\r\n\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"44.031708627888705","left":"-17.91909545661251","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.7272977112269394\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.22479242263560417\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.29776381938511776\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7272977112269394\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}"]}
\ No newline at end of file
diff --git a/programs/machines/Roland/mill/SRM-20/mill 2.5D stl connect b/programs/machines/Roland/mill/SRM-20/mill 2.5D stl connect
new file mode 100644
index 0000000000000000000000000000000000000000..769f2d214774507850cd1750e9bb26523581c8ad
--- /dev/null
+++ b/programs/machines/Roland/mill/SRM-20/mill 2.5D stl connect	
@@ -0,0 +1 @@
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"642.7985710158229","left":"3548.883638471559","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"992.7985710158231","left":"4032.883638471559","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1086.7985710158232","left":"3586.883638471559","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = '0.5'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"543.7985710158231","left":"3132.883638471559","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"991.7985710158231","left":"3167.883638471559","inputs":{},"outputs":{}},"0.3040697193095865":{"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":"645.4914883550476","left":"1561.5431668558717","inputs":{},"outputs":{}},"0.7667165137781767":{"definition":"//\n// offset\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// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = '19.394860006967658'\n   mod.distances = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         if ((mod.offset.value != '') && (mod.distances != ''))\n            offset()\n         else\n            mod.distances = ''\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"534.3490296866185","left":"4143.00249686672","inputs":{},"outputs":{}},"0.32304064019646705":{"definition":"//\n// mesh slice raster\n// \n// todo\n//    include slice plane triangles\n//    scale perturbation to resolution\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 slice raster'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '1'\n   mod.inunits.value = '0.03937007874015748'\n   mod.depth.value = '35'\n   mod.width.value = '1000'\n   mod.border.value = '10'\n   mod.delta = 1e-6\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_slice()}},\n   settings:{type:'',\n      event:function(evt){\n         for (var p in evt.detail)\n            if (p == 'depthmm') {\n               mod.depth.value = evt.detail[p]\n                  /parseFloat(mod.mmunits.value)\n               }\n         find_limits_slice()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)\n         }},\n   imageInfo:{type:'',\n      event:function(){\n         var obj = {}\n         obj.name = \"mesh slice raster\"\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         obj.dpi = mod.img.width/(mod.dx*parseFloat(mod.inunits.value))\n         mods.output(mod,'imageInfo',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen slice 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.slicecanvas = 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_slice()\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_slice()\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   // slice depth\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice Z depth: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.depth = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view slice\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 slice'))\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 slice\n//\nfunction find_limits_slice() {\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      slice_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border,delta:mod.delta})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var depth = evt.data.depth\n      var border = evt.data.border\n      var delta = evt.data.delta // perturb to remove degeneracies\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)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\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// slice mesh\n//   \nfunction slice_mesh() {\n   var blob = new Blob(['('+slice_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.slicecanvas.height*.5*(1-h/w)\n         var wd = mod.slicecanvas.width\n         var hd = mod.slicecanvas.width*h/w\n         }\n      else {\n         var x0 = mod.slicecanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.slicecanvas.height*w/h\n         var hd = mod.slicecanvas.height\n         }\n      var ctx = mod.slicecanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      })\n   var ctx = mod.slicecanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n   var depth = parseFloat(mod.depth.value)\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   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,depth:depth,\n      imgbuffer:img.data.buffer,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      delta:mod.delta},\n      [img.data.buffer])\n   }\nfunction slice_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var depth = evt.data.depth\n      var view = evt.data.mesh\n      var delta = evt.data.delta // perturb to remove degeneracies\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      //\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 slice image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] = 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      // find triangles crossing the slice\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)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         //\n         // assemble vertices\n         //\n         offset += 2\n         var v = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]\n         //\n         // sort z\n         //\n         v.sort(function(a,b) {\n            if (a[2] < b[2])\n               return -1\n            else if (a[2] > b[2])\n               return 1\n            else\n               return 0\n            })\n         //\n         // check for crossings\n         //\n         if ((v[0][2] < (zmax-depth)) && (v[2][2] > (zmax-depth))) {\n            //\n            //  crossing found, check for side and save\n            //\n            if (v[1][2] < (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[2][0]+(v[1][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               var y1 = v[2][1]+(v[1][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               }\n            else if (v[1][2] >= (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[1][0]+(v[0][0]-v[1][0])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               var y1 = v[1][1]+(v[0][1]-v[1][1])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               }\n            if (y0 < y1)\n               segs.push({x0:x0,y0:y0,x1:x1,y1:y1})\n            else\n               segs.push({x0:x1,y0:y1,x1:x0,y1:y0})\n            }\n         }\n      //\n      // fill interior\n      //\n      for (var row = 0; row < h; ++row) {\n         var y = ymin+(ymax-ymin)*row/(h-1)\n         rowsegs = segs.filter(p => ((p.y0 <= y) && (p.y1 >= y)))\n         var xs = rowsegs.map(p =>\n            (p.x0+(p.x1-p.x0)*(y-p.y0)/(p.y1-p.y0)))\n         xs.sort((a,b) => (a-b))\n         for (var col = 0; col < w; ++col) {\n            var x = xmin+(xmax-xmin)*col/(w-1)\n            var index = xs.findIndex((p) => (p >= x))\n            if (index == -1)\n               var i = 0\n            else\n               var i = 255*(index%2)\n            buf[(h-1-row)*w*4+col*4+0] = i\n            buf[(h-1-row)*w*4+col*4+1] = i\n            buf[(h-1-row)*w*4+col*4+2] = i\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // output the slice\n      //\n      self.postMessage({buffer:buf.buffer},[buf.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":"484.2962306652032","left":"1997.6209440086457","inputs":{},"outputs":{}},"0.4144526456371104":{"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":"1406.5777561710643","left":"2836.3726137158815","inputs":{},"outputs":{}},"0.9325875387173613":{"definition":"//\n// mill raster 2.5D\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// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2.5D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.1968503937007874'\n   mod.dia_mm.value = '5'\n   mod.cut_in.value = '0.3937007874015748'\n   mod.cut_mm.value = '10'\n   mod.max_in.value = '1.3779527559055118'\n   mod.max_mm.value = '35'\n   mod.number.value = '1'\n   mod.stepover.value = '0.5'\n   mod.merge.value = '1'\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            //\n            // calculation in progress, draw and accumulate\n            //\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value))\n               && (evt.detail.length > 0)) {\n               //\n               // layer detail present and offset not complete\n               //\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else if (mod.depthmm < parseFloat(mod.max_mm.value)) {\n               //\n               // layer loop not complete\n               //\n               merge_layer()\n               accumulate_toolpath()\n               clear_layer()\n               mod.depthmm += parseFloat(mod.cut_mm.value)\n               if (mod.depthmm > parseFloat(mod.max_mm.value)) {\n                  mod.depthmm = parseFloat(mod.max_mm.value)\n                  }\n               //\n               // clear offset\n               //\n               outputs.offset.event('')\n               //\n               // set new depth\n               //\n               outputs.depth.event(mod.depthmm)\n               //\n               // set new offset\n               //\n               mod.offsetCount = 0\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else {\n               //\n               // done, finish and output\n               //\n               draw_path(mod.toolpath)\n               draw_connections(mod.toolpath)\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   depth:{type:'',\n      event:function(depth){\n         mods.output(mod,'depth',{depthmm:depth})\n         }\n      },\n   diameter:{type:'',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'',\n      event:function(size){\n         mods.output(mod,'offset',size)\n         }\n      },\n   toolpath:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.toolpath\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         outputs.offset.event('') // clear offset\n         mod.depthmm = parseFloat(mod.cut_mm.value)\n         outputs.depth.event(mod.depthmm) // set depth\n         mod.toolpath = [] // start new toolpath\n         clear_layer() // clear layer\n         outputs.diameter.event()\n         outputs.offset.event( // set offset\n            mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_layer\n//\nfunction clear_layer() {\n   mod.path = []\n   mod.offset = 0.5\n   mod.offsetCount = 0\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute(\n      'viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_layer\n//\nfunction merge_layer() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// accumulate_toolpath\n//\nfunction accumulate_toolpath() {\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var newseg = []\n      for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n         var idepth = -Math.round(mod.dpi*mod.depthmm/25.4)\n         var point = mod.path[seg][pt].concat(idepth)\n         newseg.splice(newseg.length,0,point)\n         }\n      mod.toolpath.push(newseg)\n      }\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS(\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            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS(\n                  'http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute(\n                  'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS(\n         'http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = path[segment-1][path[segment-1].length-1][0]\n      var y1 = h-path[segment-1][path[segment-1].length-1][1]-1\n      var x2 = path[segment][0][0]\n      var y2 = h-path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS(\n            'http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute(\n            'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"623.0093202529583","left":"2555.700932035969","inputs":{},"outputs":{}},"0.9327347189729929":{"definition":"//\r\n// STL module extracts stl from Tools/FabLab Connect command of SolidWorks products\r\n// \r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2019\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'STL connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function () {\r\n    mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n    mod.port = getParameterByName('swPort') || '80'\r\n    mod.socket = 0\r\n   mod.sag.value = '0.1'\n   mod.angle.value = '45'\n    socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   }\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   mesh:{type:'STL',\r\n      event:function(buffer){\r\n         mods.output(mod,'mesh',buffer)}}\r\n      }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // canvas\r\n   //\r\n   var canvas = document.createElement('canvas')\r\n      canvas.width = mods.ui.canvas\r\n      canvas.height = mods.ui.canvas\r\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\r\n      div.appendChild(canvas)\r\n      mod.canvas = canvas\r\n      div.appendChild(document.createElement('br'))\r\n\r\n      div.appendChild(document.createTextNode('server:'))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 12\r\n      div.appendChild(input)\r\n      mod.status = input\r\n      div.appendChild(document.createElement('br'))\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click', function () {\r\n          socket_open()\r\n      })\r\n      div.appendChild(btn)\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click', function () {\r\n          socket_close()\r\n      })\r\n      div.appendChild(btn)\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('sag: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.sag = input\r\n      div.appendChild(document.createTextNode('(mm)'))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('angle: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.angle = input\r\n      div.appendChild(document.createTextNode('(degree)'))\r\n      div.appendChild(document.createElement('br'))\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Extract STL'))\r\n      btn.addEventListener('click', function () {\r\n          extract_STL()\r\n      })\r\n      div.appendChild(btn)\r\n\r\n   //\r\n   // info\r\n   //\r\n   var info = document.createElement('div')\r\n   info.setAttribute('id', div.id + 'info')\r\n       var text = document.createTextNode('name: ')\n       info.appendChild(text)\n       mod.name = text\r\n       info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('size: ')\r\n         info.appendChild(text)\r\n         mod.sizen = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('triangles: ')\r\n         info.appendChild(text)\r\n         mod.trianglesn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dx: ')\r\n         info.appendChild(text)\r\n         mod.dxn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dy: ')\r\n         info.appendChild(text)\r\n         mod.dyn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dz: ')\r\n         info.appendChild(text)\r\n         mod.dzn = text\r\n         div.appendChild(info)\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\n    //\r\n    // local functions\r\n    //\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href;\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url);\r\n    if (!results) return null;\r\n    if (!results[2]) return '';\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\r\n}\r\n\r\nfunction socket_open() {\r\n    var url = \"ws://\" + mod.address + ':' + mod.port\r\n    mod.socket = new WebSocket(url)\r\n    mod.socket.onopen = function (event) {\r\n        mod.status.value = \"opened\"\r\n        var connect = {}\r\n        connect.modCmd = 'connect'\r\n        connect.owner = getParameterByName('swOwner')\r\n        connect.id = getParameterByName('swID')\r\n        socket_send(JSON.stringify(connect))\r\n    }\r\n    mod.socket.onerror = function (event) {\r\n        mod.status.value = \"can not open\"\r\n    }\r\n    mod.socket.onmessage = function (event) {\r\n        mod.status.value = \"receive\"\r\n        var swData = JSON.parse(event.data)\r\n        if (swData.swType === \"AutoExtractSTL\") {\r\n            var partName = swData.data.partName\r\n            if (partName.length > 25)\r\n                partName = partName.slice(0, 22) +'...'\r\n            mod.name.nodeValue = \"name: \" + partName\r\n            mod.str = swData.data.stl\r\n            stl_load_handler()\r\n        }\r\n    }\r\n    mod.socket.onclose = function (event) {\r\n        mod.status.value = \"connection closed\"\r\n    }\r\n}\r\nfunction socket_close() {\r\n    mod.socket.close()\r\n    mod.status.value = \"closed\"\r\n    mod.socket = 0\r\n}\r\nfunction socket_send(msg) {\r\n    if (mod.socket != 0) {\r\n        mod.status.value = \"send\"\r\n        mod.socket.send(msg)\r\n    }\r\n    else {\r\n        mod.status.value = \"can't send, not open\"\r\n    }\r\n}\r\nfunction extract_STL() {\r\n    var modcmd = new Object;\r\n    modcmd.modCmd = \"AutoExtractSTL\";\r\n    modcmd.sag = Number(mod.sag.value); // mm\r\n    modcmd.angle = Number(mod.angle.value); //degree\r\n    socket_send(JSON.stringify(modcmd))\r\n}\r\n\r\nfunction base64ToArrayBuffer(base64) {\r\n    var binary_string = window.atob(base64);\r\n    var len = binary_string.length;\r\n    var bytes = new Uint8Array(len);\r\n    for (var i = 0; i < len; i++) {\r\n        bytes[i] = binary_string.charCodeAt(i);\r\n    }\r\n    return bytes.buffer;\r\n}\r\n\r\n//\r\n// load handler\r\n//\r\nfunction stl_load_handler() {\r\n   //\r\n   // check for binary STL\r\n    //\r\n   var arraybuf = base64ToArrayBuffer(mod.str)\r\n   var endian = true\r\n   var view = new DataView(arraybuf)\r\n   var triangles = view.getUint32(80, endian)\r\n   var size = 80+4+triangles*(4*12+2)\r\n   if (size != view.byteLength) {\r\n      mod.sizen.nodeValue = 'error: not binary STL'\r\n      mod.trianglesn.nodeValue = ''\r\n      mod.dxn.nodeValue = ''\r\n      mod.dyn.nodeValue = ''\r\n      mod.dzn.nodeValue = ''\r\n      return\r\n      }\r\n   mod.sizen.nodeValue = 'size: '+size\r\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\r\n   //\r\n   // find limits and draw\r\n   //\r\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\r\n   var url = window.URL.createObjectURL(blob)\r\n   var webworker = new Worker(url)\r\n   webworker.addEventListener('message',function(evt) {\r\n      //\r\n      // worker response\r\n      //\r\n      window.URL.revokeObjectURL(url)\r\n      //\r\n      // size\r\n      //\r\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\r\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\r\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\r\n      //\r\n      // image\r\n      //\r\n      var image = evt.data.image\r\n      var height = mod.canvas.height\r\n      var width = mod.canvas.width\r\n      var buffer = new Uint8ClampedArray(evt.data.image)\r\n      var imgdata = new ImageData(buffer,width,height)\r\n      var ctx = mod.canvas.getContext(\"2d\")\r\n      ctx.putImageData(imgdata,0,0)\r\n      //\r\n      // output\r\n      //\r\n      outputs.mesh.event(evt.data.mesh)\r\n      })\r\n   var ctx = mod.canvas.getContext(\"2d\")\r\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\r\n   var img = ctx.getImageData(0, 0, mod.canvas.width, mod.canvas.height)\r\n   //\r\n   // call worker\r\n   //\r\n   webworker.postMessage({\r\n      height:mod.canvas.height,width:mod.canvas.width,\r\n      image: img.data.buffer, mesh: arraybuf},\r\n      [img.data.buffer, arraybuf])\r\n   }\r\nfunction draw_limits_worker() {\r\n   self.addEventListener('message',function(evt) {\r\n      //\r\n      // function to draw line\r\n      //\r\n      function line(x0,y0,x1,y1) {\r\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\r\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\r\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\r\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\r\n         var row,col\r\n         var idx = ix1-ix0\r\n         var idy = iy1-iy0\r\n         if (Math.abs(idy) > Math.abs(idx)) {\r\n            (idy > 0) ?\r\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\r\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\r\n            for (row = row0; row <= row1; ++row) {\r\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\r\n               image[row*width*4+col*4+0] = 0\r\n               image[row*width*4+col*4+1] = 0\r\n               image[row*width*4+col*4+2] = 0\r\n               image[row*width*4+col*4+3] = 255\r\n               }\r\n            }\r\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\r\n            (idx > 0) ?\r\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\r\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\r\n            for (col = col0; col <= col1; ++col) {\r\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\r\n               image[row*width*4+col*4+0] = 0\r\n               image[row*width*4+col*4+1] = 0\r\n               image[row*width*4+col*4+2] = 0\r\n               image[row*width*4+col*4+3] = 255\r\n               }\r\n            }\r\n         else {\r\n            row = iy0\r\n            col = ix0\r\n            image[row*width*4+col*4+0] = 0\r\n            image[row*width*4+col*4+1] = 0\r\n            image[row*width*4+col*4+2] = 0\r\n            image[row*width*4+col*4+3] = 255\r\n            }\r\n         }\r\n      //\r\n      // get variables\r\n      //\r\n      var height = evt.data.height\r\n      var width = evt.data.width\r\n      var endian = true\r\n      var image = new Uint8ClampedArray(evt.data.image)\r\n      var view = new DataView(evt.data.mesh)\r\n      var triangles = view.getUint32(80,endian)\r\n      //\r\n      // find limits\r\n      //\r\n      var offset = 80+4\r\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\r\n      var xmin = Number.MAX_VALUE\r\n      var xmax = -Number.MAX_VALUE\r\n      var ymin = Number.MAX_VALUE\r\n      var ymax = -Number.MAX_VALUE\r\n      var zmin = Number.MAX_VALUE\r\n      var zmax = -Number.MAX_VALUE\r\n      for (var t = 0; t < triangles; ++t) {\r\n         offset += 3*4\r\n         x0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x0 > xmax) xmax = x0\r\n         if (x0 < xmin) xmin = x0\r\n         y0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y0 > ymax) ymax = y0\r\n         if (y0 < ymin) ymin = y0\r\n         z0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z0 > zmax) zmax = z0\r\n         if (z0 < zmin) zmin = z0\r\n         x1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x1 > xmax) xmax = x1\r\n         if (x1 < xmin) xmin = x1\r\n         y1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y1 > ymax) ymax = y1\r\n         if (y1 < ymin) ymin = y1\r\n         z1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z1 > zmax) zmax = z1\r\n         if (z1 < zmin) zmin = z1\r\n         x2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x2 > xmax) xmax = x2\r\n         if (x2 < xmin) xmin = x2\r\n         y2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y2 > ymax) ymax = y2\r\n         if (y2 < ymin) ymin = y2\r\n         z2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z2 > zmax) zmax = z2\r\n         if (z2 < zmin) zmin = z2\r\n         offset += 2\r\n         }\r\n      var dx = xmax-xmin\r\n      var dy = ymax-ymin\r\n      var dz = zmax-zmin\r\n      //\r\n      // draw mesh\r\n      //\r\n      if (dx > dy) {\r\n         var xo = 0\r\n         var yo = height*.5*(1-dy/dx)\r\n         var xw = width-1\r\n         var yh = (width-1)*dy/dx\r\n         }\r\n      else {\r\n         var xo = width*.5*(1-dx/dy)\r\n         var yo = 0\r\n         var xw = (height-1)*dx/dy\r\n         var yh = height-1\r\n         }\r\n      offset = 80+4\r\n      for (var t = 0; t < triangles; ++t) {\r\n         offset += 3*4\r\n         x0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         x1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         x2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         offset += 2\r\n         line(x0,y0,x1,y1)\r\n         line(x1,y1,x2,y2)\r\n         line(x2,y2,x0,y0)\r\n         }\r\n      //\r\n      // return results and close\r\n      //\r\n      self.postMessage({\r\n         dx:dx,dy:dy,dz:dz,\r\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\r\n      self.close()\r\n      })\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"423.0822992185986","left":"1114.1152264935706","inputs":{},"outputs":{}},"0.14129137871043707":{"definition":"//\r\n// Roland SRM-20 milling machine\r\n//\r\n// Neil Gershenfeld\r\n// (c) Massachusetts Institute of Technology 2016\r\n//\r\n// This work may be reproduced, modified, distributed, performed, and\r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is\r\n// provided as is; no warranty is provided, and users accept all\r\n// liability.\r\n//\r\n// closure\r\n//\r\n/*\r\nG-codes:\r\nG00X10.0\r\nG90 (absolute positioning)\r\nG21 (mm units)\r\n#.0 numbers\r\nG00 (positioning rapid move)\r\nG01 (linear motion)\r\nM03 (start spindle)\r\nM05 (stop spindle)\r\nF (feed rate mm/min, 6-1800)\r\n203.2 (X) x 152.4 (Y) x 60.5 (Z) mm         \r\n*/\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'Roland SRM-20 milling machine'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.units = 100.0\r\n   mod.speed.value = 4\r\n   mod.ox.value = 10\r\n   mod.oy.value = 10\r\n   mod.oz.value = 10\r\n   mod.jz.value = 2\r\n   mod.hx.value = 0\r\n   mod.hy.value = 152.4\r\n   mod.hz.value = 60.5\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   path:{type:'',\r\n      event:function(evt){\r\n         mod.name = evt.detail.name\r\n         mod.path = evt.detail.path\r\n         mod.dpi = evt.detail.dpi\r\n         mod.width = evt.detail.width\r\n         mod.height = evt.detail.height\r\n         make_path()\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   file:{type:'',\r\n      event:function(obj){\r\n         mods.output(mod,'file',obj)\r\n         }}}\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // speed\r\n   //\r\n   div.appendChild(document.createTextNode('speed: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.speed = input\r\n   div.appendChild(document.createTextNode(' (mm/s)'))\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // origin\r\n   //\r\n   div.appendChild(document.createTextNode('origin:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('x: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.ox = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode(' y: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.oy = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('z: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.oz = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('move to origin')\r\n            span.appendChild(text)\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         var x0 = mod.units*parseFloat(mod.ox.value);\r\n         var y0 = mod.units*parseFloat(mod.oy.value);\r\n         var z0 = mod.units*parseFloat(mod.oz.value);\r\n         var zjog = z0+mod.units*parseFloat(mod.jz.value);\r\n         //\r\n         // G-code version\r\n         //\r\n         /*\r\n         str = '%\\n' // data start\r\n         str += 'G90\\n' // absolute units\r\n         str += 'G21\\n' // mm units\r\n         str += 'G00Z30.0\\n' // move z\r\n         str += 'M02\\n' // end of program\r\n         */\r\n         //\r\n         // RML version\r\n         //\r\n         var str = \"PA;PA;VS10;!VZ10;!PZ0,\"+zjog+\";PU\"+x0+\",\"+y0+\";Z\"+x0+\",\"+y0+\",\"+z0+\";!MC0;\"+\"\\u0004\"\r\n         //\r\n         // send command\r\n         //\r\n         var obj = {}\r\n         obj.type = 'command'\r\n         obj.name = mod.name+'.rml'\r\n         obj.contents = str\r\n         outputs.file.event(obj)\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // jog\r\n   //\r\n   div.appendChild(document.createTextNode('jog height:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('z: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.jz = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // home\r\n   //\r\n   div.appendChild(document.createTextNode('home:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('x: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.hx = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode(' y: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.hy = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('z: '))\r\n   var input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 6\r\n      div.appendChild(input)\r\n      mod.hz = input\r\n   div.appendChild(document.createTextNode(' (mm)'))\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('move to home')\r\n            span.appendChild(text)\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         var xhome = mod.units*parseFloat(mod.hx.value);\r\n         var yhome = mod.units*parseFloat(mod.hy.value);\r\n         var zhome = mod.units*parseFloat(mod.hz.value);\r\n         //\r\n         // G-code version\r\n         //\r\n         /*\r\n         str = '%\\n' // data start\r\n         str += 'G90\\n' // absolute units\r\n         str += 'G21\\n' // mm units\r\n         str += 'G00Z50.0\\n' // move z\r\n         str += 'M02\\n' // end of program\r\n         */\r\n         //\r\n         // RML version\r\n         //\r\n         var str = \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"+\"\\u0004\"\r\n         //\r\n         // send command\r\n         //\r\n         var obj = {}\r\n         obj.type = 'command'\r\n         obj.name = mod.name+'.rml'\r\n         obj.contents = str\r\n         outputs.file.event(obj)\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction make_path() {\r\n   var dx = 25.4*mod.width/mod.dpi\r\n   var nx = mod.width\r\n   var speed = parseFloat(mod.speed.value)\r\n   var jog = parseFloat(mod.oz.value)+parseFloat(mod.jz.value)\r\n   var ijog = Math.floor(mod.units*jog)\r\n   var scale = mod.units*dx/(nx-1)\r\n   var x0 = parseFloat(mod.ox.value)\r\n   var y0 = parseFloat(mod.oy.value)\r\n   var z0 = parseFloat(mod.oz.value)\r\n   var xoffset = mod.units*x0\r\n   var yoffset = mod.units*y0\r\n   var zoffset = mod.units*z0\r\n   var str = \"PA;PA;\" // plot absolute\r\n   str += \"VS\"+speed+\";!VZ\"+speed+\";\"\r\n   str += \"!PZ\"+0+\",\"+ijog+\";\" // set jog\r\n   str += \"!MC1;\\n\" // turn motor on\r\n   //\r\n   // follow segments\r\n   //\r\n   for (var seg = 0; seg < mod.path.length; ++seg) {\r\n      //\r\n      // move up to starting point\r\n      //\r\n      x = xoffset+scale*mod.path[seg][0][0]\r\n      y = yoffset+scale*mod.path[seg][0][1]\r\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\r\n      //\r\n      // move down\r\n      //\r\n      z = zoffset+scale*mod.path[seg][0][2]\r\n      str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\r\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\r\n         //\r\n         // move to next point\r\n         //\r\n         x = xoffset+scale*mod.path[seg][pt][0]\r\n         y = yoffset+scale*mod.path[seg][pt][1]\r\n         z = zoffset+scale*mod.path[seg][pt][2]\r\n         str += \"Z\"+x.toFixed(0)+\",\"+y.toFixed(0)+\",\"+z.toFixed(0)+\";\\n\"\r\n         }\r\n      //\r\n      // move up\r\n      //\r\n      str += \"PU\"+x.toFixed(0)+\",\"+y.toFixed(0)+\";\\n\"\r\n      }\r\n   //\r\n   // turn off motor and move back\r\n   //\r\n   var xhome = mod.units*parseFloat(mod.hx.value)\r\n   var yhome = mod.units*parseFloat(mod.hy.value)\r\n   var zhome = mod.units*parseFloat(mod.hz.value)\r\n   str += \"PA;PA;!PZ0,\"+zhome+\";PU\"+xhome+\",\"+yhome+\";!MC0;\"\r\n   //\r\n   // output string\r\n   //\r\n   var obj = {}\r\n   obj.type = 'file'\r\n   obj.name = mod.name+'.rml'\r\n   obj.contents = str\r\n   outputs.file.event(obj)\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"1216.7552078317794","left":"1704.718114805777","inputs":{},"outputs":{}},"0.7206126800940682":{"definition":"//\r\n// print server module\r\n//\r\n// Neil Gershenfeld \r\n// (c) Massachusetts Institute of Technology 2017\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'WebSocket print'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address.value = '127.0.0.1'\n   mod.port.value = '1234'\n   mod.printers.value = 'Printer not found'\n   mod.socket = null\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   file:{type:'',\r\n      event:function(evt){\r\n         if (evt.detail.type == 'file') {\r\n            mod.job = evt.detail\r\n            mod.label.nodeValue = 'send file to printer'\r\n            mod.labelspan.style.fontWeight = 'bold'\r\n            }\r\n         else if (evt.detail.type == 'command') {\r\n            mod.job = evt.detail\r\n            mod.job.printer = mod.printers.value\r\n            socket_send(JSON.stringify(mod.job))\r\n            }\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // server\r\n   //\r\n   var a = document.createElement('a')\r\n      a.href = './js/printserver.js'\r\n      a.innerHTML = 'printserver:'\r\n      a.target = '_blank'\r\n   div.appendChild(a)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.address = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.port = input\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // open/close\r\n   //\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click',function() {\r\n         socket_open()\r\n         })\r\n      div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click',function() {\r\n         socket_close()\r\n         })\r\n      div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   //\r\n   // printer\r\n   //\r\n   div.appendChild(document.createTextNode('printer:'))\r\n       div.appendChild(document.createElement('br'))\r\n       var select = document.createElement('select')\r\n       select.setAttribute('style', 'width:150px');\r\n      var el = document.createElement('option')\r\n          el.textContent = 'Printer not found'\r\n          el.value = 'Printer not found'\r\n          select.appendChild(el)\r\n       div.appendChild(select)\r\n       mod.printers = select \r\n   div.appendChild(document.createElement('br'))   \r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      var span = document.createElement('span')\r\n         var text = document.createTextNode('waiting for file')\r\n            mod.label = text\r\n            span.appendChild(text)\r\n         mod.labelspan = span\r\n         btn.appendChild(span)\r\n      btn.addEventListener('click',function(){\r\n         if (mod.socket == null) {\r\n            mod.status.value = \"can't send, not open\"\r\n            }\r\n         else if (mod.label.nodeValue == 'send file to printer') {\r\n            mod.job.printer = mod.printers.value\r\n            socket_send(JSON.stringify(mod.job))\r\n            mod.label.nodeValue = 'cancel'\r\n            }\r\n         else if (mod.label.nodeValue == 'cancel') {\r\n            socket_send('cancel')\r\n            }\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address.value+':'+mod.port.value\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n      mod.status.value = \"socket opened\"\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open socket\"\r\n      mod.socket = null\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = event.data\r\n      if ((event.data == 'done') || (event.data == 'cancel')\r\n         || (event.data.slice(0,5) == 'error')) {\r\n         mod.label.nodeValue = 'waiting for file'\r\n         mod.labelspan.style.fontWeight = 'normal'\r\n         }\r\n       else if (event.data[0] === '{'){\r\n         var printerInfo = JSON.parse(event.data)\r\n         var printerList = printerInfo['printerList']\r\n         if (printerList) {\r\n            while (mod.printers.hasChildNodes()) {\r\n               mod.printers.removeChild(mod.printers.lastChild);\r\n               }\r\n\r\n         for(var i = 0; i < printerList.length; i++){\r\n            var printer = printerList[i];\r\n            var el = document.createElement('option')\r\n            el.textContent = printer\r\n            el.value = printer\r\n            mod.printers.appendChild(el)\r\n            }\r\n            var defaultPrinter = printerInfo['default']\r\n            if(defaultPrinter && defaultPrinter.length>0){\r\n               mod.printers.value = defaultPrinter\r\n               }\r\n            }\r\n         }\r\n      }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"socket closed\"\r\n   mod.socket = null\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != null) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"1351.066486212147","left":"2188.8434424353395","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"depth\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9327347189729929\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.14129137871043707\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.14129137871043707\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7206126800940682\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4144526456371104\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}"]}
\ No newline at end of file
diff --git a/programs/machines/ShopBot/mill 2.5D stl connect b/programs/machines/ShopBot/mill 2.5D stl connect
new file mode 100644
index 0000000000000000000000000000000000000000..218b217c3fa2e3812df5f93d3cdd55f650c18ed9
--- /dev/null
+++ b/programs/machines/ShopBot/mill 2.5D stl connect	
@@ -0,0 +1 @@
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"563.8000042768857","left":"3159.386325836052","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"913.8000042768859","left":"3643.386325836052","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1007.8000042768859","left":"3197.386325836052","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = '0.5'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"464.800004276886","left":"2743.386325836052","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"912.8000042768859","left":"2778.386325836052","inputs":{},"outputs":{}},"0.4793941661670936":{"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":"1404.0054355376824","left":"1620.6631658186536","inputs":{},"outputs":{}},"0.7562574507163453":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '20'\n   mod.plungespeed.value = '20'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '5'\n   mod.spindlespeed.value = '10000'\n   mod.unitsin.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"1073.2516012544538","left":"1643.895753383433","inputs":{},"outputs":{}},"0.3040697193095865":{"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":"566.4929216161105","left":"1172.0458542203646","inputs":{},"outputs":{}},"0.7667165137781767":{"definition":"//\n// offset\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// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = '25'\n   mod.distances = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         if ((mod.offset.value != '') && (mod.distances != ''))\n            offset()\n         else\n            mod.distances = ''\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"455.3504629476813","left":"3753.5051842312128","inputs":{},"outputs":{}},"0.32304064019646705":{"definition":"//\n// mesh slice raster\n// \n// todo\n//    include slice plane triangles\n//    scale perturbation to resolution\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 slice raster'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '1'\n   mod.inunits.value = '0.03937007874015748'\n   mod.depth.value = '5'\n   mod.width.value = '1000'\n   mod.border.value = '0'\n   mod.delta = 1e-6\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_slice()}},\n   settings:{type:'',\n      event:function(evt){\n         for (var p in evt.detail)\n            if (p == 'depthmm') {\n               mod.depth.value = evt.detail[p]\n                  /parseFloat(mod.mmunits.value)\n               }\n         find_limits_slice()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)\n         }},\n   imageInfo:{type:'',\n      event:function(){\n         var obj = {}\n         obj.name = \"mesh slice raster\"\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         obj.dpi = mod.img.width/(mod.dx*parseFloat(mod.inunits.value))\n         mods.output(mod,'imageInfo',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen slice 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.slicecanvas = 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_slice()\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_slice()\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   // slice depth\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice Z depth: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.depth = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view slice\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 slice'))\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 slice\n//\nfunction find_limits_slice() {\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      slice_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border,delta:mod.delta})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var depth = evt.data.depth\n      var border = evt.data.border\n      var delta = evt.data.delta // perturb to remove degeneracies\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)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\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// slice mesh\n//   \nfunction slice_mesh() {\n   var blob = new Blob(['('+slice_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.slicecanvas.height*.5*(1-h/w)\n         var wd = mod.slicecanvas.width\n         var hd = mod.slicecanvas.width*h/w\n         }\n      else {\n         var x0 = mod.slicecanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.slicecanvas.height*w/h\n         var hd = mod.slicecanvas.height\n         }\n      var ctx = mod.slicecanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      })\n   var ctx = mod.slicecanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n   var depth = parseFloat(mod.depth.value)\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   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,depth:depth,\n      imgbuffer:img.data.buffer,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      delta:mod.delta},\n      [img.data.buffer])\n   }\nfunction slice_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var depth = evt.data.depth\n      var view = evt.data.mesh\n      var delta = evt.data.delta // perturb to remove degeneracies\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      //\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 slice image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] = 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      // find triangles crossing the slice\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)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         //\n         // assemble vertices\n         //\n         offset += 2\n         var v = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]\n         //\n         // sort z\n         //\n         v.sort(function(a,b) {\n            if (a[2] < b[2])\n               return -1\n            else if (a[2] > b[2])\n               return 1\n            else\n               return 0\n            })\n         //\n         // check for crossings\n         //\n         if ((v[0][2] < (zmax-depth)) && (v[2][2] > (zmax-depth))) {\n            //\n            //  crossing found, check for side and save\n            //\n            if (v[1][2] < (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[2][0]+(v[1][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               var y1 = v[2][1]+(v[1][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               }\n            else if (v[1][2] >= (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[1][0]+(v[0][0]-v[1][0])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               var y1 = v[1][1]+(v[0][1]-v[1][1])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               }\n            if (y0 < y1)\n               segs.push({x0:x0,y0:y0,x1:x1,y1:y1})\n            else\n               segs.push({x0:x1,y0:y1,x1:x0,y1:y0})\n            }\n         }\n      //\n      // fill interior\n      //\n      for (var row = 0; row < h; ++row) {\n         var y = ymin+(ymax-ymin)*row/(h-1)\n         rowsegs = segs.filter(p => ((p.y0 <= y) && (p.y1 >= y)))\n         var xs = rowsegs.map(p =>\n            (p.x0+(p.x1-p.x0)*(y-p.y0)/(p.y1-p.y0)))\n         xs.sort((a,b) => (a-b))\n         for (var col = 0; col < w; ++col) {\n            var x = xmin+(xmax-xmin)*col/(w-1)\n            var index = xs.findIndex((p) => (p >= x))\n            if (index == -1)\n               var i = 0\n            else\n               var i = 255*(index%2)\n            buf[(h-1-row)*w*4+col*4+0] = i\n            buf[(h-1-row)*w*4+col*4+1] = i\n            buf[(h-1-row)*w*4+col*4+2] = i\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // output the slice\n      //\n      self.postMessage({buffer:buf.buffer},[buf.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":"405.29766392626607","left":"1608.1236313731386","inputs":{},"outputs":{}},"0.4144526456371104":{"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":"1305.2167006691657","left":"2161.848323325338","inputs":{},"outputs":{}},"0.9325875387173613":{"definition":"//\n// mill raster 2.5D\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// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2.5D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.1'\n   mod.dia_mm.value = '2.54'\n   mod.cut_in.value = '0.1'\n   mod.cut_mm.value = '2.54'\n   mod.max_in.value = '1'\n   mod.max_mm.value = '25.4'\n   mod.number.value = '1'\n   mod.stepover.value = '0.5'\n   mod.merge.value = '1'\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            //\n            // calculation in progress, draw and accumulate\n            //\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value))\n               && (evt.detail.length > 0)) {\n               //\n               // layer detail present and offset not complete\n               //\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else if (mod.depthmm < parseFloat(mod.max_mm.value)) {\n               //\n               // layer loop not complete\n               //\n               merge_layer()\n               accumulate_toolpath()\n               clear_layer()\n               mod.depthmm += parseFloat(mod.cut_mm.value)\n               if (mod.depthmm > parseFloat(mod.max_mm.value)) {\n                  mod.depthmm = parseFloat(mod.max_mm.value)\n                  }\n               //\n               // clear offset\n               //\n               outputs.offset.event('')\n               //\n               // set new depth\n               //\n               outputs.depth.event(mod.depthmm)\n               //\n               // set new offset\n               //\n               mod.offsetCount = 0\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else {\n               //\n               // done, finish and output\n               //\n               draw_path(mod.toolpath)\n               draw_connections(mod.toolpath)\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   depth:{type:'',\n      event:function(depth){\n         mods.output(mod,'depth',{depthmm:depth})\n         }\n      },\n   diameter:{type:'',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'',\n      event:function(size){\n         mods.output(mod,'offset',size)\n         }\n      },\n   toolpath:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.toolpath\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         outputs.offset.event('') // clear offset\n         mod.depthmm = parseFloat(mod.cut_mm.value)\n         outputs.depth.event(mod.depthmm) // set depth\n         mod.toolpath = [] // start new toolpath\n         clear_layer() // clear layer\n         outputs.diameter.event()\n         outputs.offset.event( // set offset\n            mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_layer\n//\nfunction clear_layer() {\n   mod.path = []\n   mod.offset = 0.5\n   mod.offsetCount = 0\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute(\n      'viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_layer\n//\nfunction merge_layer() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// accumulate_toolpath\n//\nfunction accumulate_toolpath() {\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var newseg = []\n      for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n         var idepth = -Math.round(mod.dpi*mod.depthmm/25.4)\n         var point = mod.path[seg][pt].concat(idepth)\n         newseg.splice(newseg.length,0,point)\n         }\n      mod.toolpath.push(newseg)\n      }\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS(\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            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS(\n                  'http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute(\n                  'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS(\n         'http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = path[segment-1][path[segment-1].length-1][0]\n      var y1 = h-path[segment-1][path[segment-1].length-1][1]-1\n      var x2 = path[segment][0][0]\n      var y2 = h-path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS(\n            'http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute(\n            'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"544.0107535140212","left":"2166.203619400462","inputs":{},"outputs":{}},"0.40901053044748015":{"definition":"//\r\n// STL module extracts stl from Tools/FabLab Connect command of SolidWorks products\r\n// \r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2019\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'STL connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function () {\r\n    mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n    mod.port = getParameterByName('swPort') || '80'\r\n    mod.socket = 0\r\n   mod.sag.value = '0.1'\n   mod.angle.value = '45'\n    socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   }\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   mesh:{type:'STL',\r\n      event:function(buffer){\r\n         mods.output(mod,'mesh',buffer)}}\r\n      }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   //\r\n   // canvas\r\n   //\r\n   var canvas = document.createElement('canvas')\r\n      canvas.width = mods.ui.canvas\r\n      canvas.height = mods.ui.canvas\r\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\r\n      div.appendChild(canvas)\r\n      mod.canvas = canvas\r\n      div.appendChild(document.createElement('br'))\r\n\r\n      div.appendChild(document.createTextNode('server:'))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 12\r\n      div.appendChild(input)\r\n      mod.status = input\r\n      div.appendChild(document.createElement('br'))\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('open'))\r\n      btn.addEventListener('click', function () {\r\n          socket_open()\r\n      })\r\n      div.appendChild(btn)\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('close'))\r\n      btn.addEventListener('click', function () {\r\n          socket_close()\r\n      })\r\n      div.appendChild(btn)\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('sag: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.sag = input\r\n      div.appendChild(document.createTextNode('(mm)'))\r\n      div.appendChild(document.createElement('br'))\r\n      div.appendChild(document.createTextNode('angle: '))\r\n      input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.angle = input\r\n      div.appendChild(document.createTextNode('(degree)'))\r\n      div.appendChild(document.createElement('br'))\r\n      var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Extract STL'))\r\n      btn.addEventListener('click', function () {\r\n          extract_STL()\r\n      })\r\n      div.appendChild(btn)\r\n\r\n   //\r\n   // info\r\n   //\r\n   var info = document.createElement('div')\r\n   info.setAttribute('id', div.id + 'info')\r\n       var text = document.createTextNode('name: ')\n       info.appendChild(text)\n       mod.name = text\r\n       info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('size: ')\r\n         info.appendChild(text)\r\n         mod.sizen = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('triangles: ')\r\n         info.appendChild(text)\r\n         mod.trianglesn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dx: ')\r\n         info.appendChild(text)\r\n         mod.dxn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dy: ')\r\n         info.appendChild(text)\r\n         mod.dyn = text\r\n      info.appendChild(document.createElement('br'))\r\n      var text = document.createTextNode('dz: ')\r\n         info.appendChild(text)\r\n         mod.dzn = text\r\n         div.appendChild(info)\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\n    //\r\n    // local functions\r\n    //\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href;\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url);\r\n    if (!results) return null;\r\n    if (!results[2]) return '';\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\r\n}\r\n\r\nfunction socket_open() {\r\n    var url = \"ws://\" + mod.address + ':' + mod.port\r\n    mod.socket = new WebSocket(url)\r\n    mod.socket.onopen = function (event) {\r\n        mod.status.value = \"opened\"\r\n        var connect = {}\r\n        connect.modCmd = 'connect'\r\n        connect.owner = getParameterByName('swOwner')\r\n        connect.id = getParameterByName('swID')\r\n        socket_send(JSON.stringify(connect))\r\n    }\r\n    mod.socket.onerror = function (event) {\r\n        mod.status.value = \"can not open\"\r\n    }\r\n    mod.socket.onmessage = function (event) {\r\n        mod.status.value = \"receive\"\r\n        var swData = JSON.parse(event.data)\r\n        if (swData.swType === \"AutoExtractSTL\") {\r\n            var partName = swData.data.partName\r\n            if (partName.length > 25)\r\n                partName = partName.slice(0, 22) +'...'\r\n            mod.name.nodeValue = \"name: \" + partName\r\n            mod.str = swData.data.stl\r\n            stl_load_handler()\r\n        }\r\n    }\r\n    mod.socket.onclose = function (event) {\r\n        mod.status.value = \"connection closed\"\r\n    }\r\n}\r\nfunction socket_close() {\r\n    mod.socket.close()\r\n    mod.status.value = \"closed\"\r\n    mod.socket = 0\r\n}\r\nfunction socket_send(msg) {\r\n    if (mod.socket != 0) {\r\n        mod.status.value = \"send\"\r\n        mod.socket.send(msg)\r\n    }\r\n    else {\r\n        mod.status.value = \"can't send, not open\"\r\n    }\r\n}\r\nfunction extract_STL() {\r\n    var modcmd = new Object;\r\n    modcmd.modCmd = \"AutoExtractSTL\";\r\n    modcmd.sag = Number(mod.sag.value); // mm\r\n    modcmd.angle = Number(mod.angle.value); //degree\r\n    socket_send(JSON.stringify(modcmd))\r\n}\r\n\r\nfunction base64ToArrayBuffer(base64) {\r\n    var binary_string = window.atob(base64);\r\n    var len = binary_string.length;\r\n    var bytes = new Uint8Array(len);\r\n    for (var i = 0; i < len; i++) {\r\n        bytes[i] = binary_string.charCodeAt(i);\r\n    }\r\n    return bytes.buffer;\r\n}\r\n\r\n//\r\n// load handler\r\n//\r\nfunction stl_load_handler() {\r\n   //\r\n   // check for binary STL\r\n    //\r\n   var arraybuf = base64ToArrayBuffer(mod.str)\r\n   var endian = true\r\n   var view = new DataView(arraybuf)\r\n   var triangles = view.getUint32(80, endian)\r\n   var size = 80+4+triangles*(4*12+2)\r\n   if (size != view.byteLength) {\r\n      mod.sizen.nodeValue = 'error: not binary STL'\r\n      mod.trianglesn.nodeValue = ''\r\n      mod.dxn.nodeValue = ''\r\n      mod.dyn.nodeValue = ''\r\n      mod.dzn.nodeValue = ''\r\n      return\r\n      }\r\n   mod.sizen.nodeValue = 'size: '+size\r\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\r\n   //\r\n   // find limits and draw\r\n   //\r\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\r\n   var url = window.URL.createObjectURL(blob)\r\n   var webworker = new Worker(url)\r\n   webworker.addEventListener('message',function(evt) {\r\n      //\r\n      // worker response\r\n      //\r\n      window.URL.revokeObjectURL(url)\r\n      //\r\n      // size\r\n      //\r\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\r\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\r\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\r\n      //\r\n      // image\r\n      //\r\n      var image = evt.data.image\r\n      var height = mod.canvas.height\r\n      var width = mod.canvas.width\r\n      var buffer = new Uint8ClampedArray(evt.data.image)\r\n      var imgdata = new ImageData(buffer,width,height)\r\n      var ctx = mod.canvas.getContext(\"2d\")\r\n      ctx.putImageData(imgdata,0,0)\r\n      //\r\n      // output\r\n      //\r\n      outputs.mesh.event(evt.data.mesh)\r\n      })\r\n   var ctx = mod.canvas.getContext(\"2d\")\r\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\r\n   var img = ctx.getImageData(0, 0, mod.canvas.width, mod.canvas.height)\r\n   //\r\n   // call worker\r\n   //\r\n   webworker.postMessage({\r\n      height:mod.canvas.height,width:mod.canvas.width,\r\n      image: img.data.buffer, mesh: arraybuf},\r\n      [img.data.buffer, arraybuf])\r\n   }\r\nfunction draw_limits_worker() {\r\n   self.addEventListener('message',function(evt) {\r\n      //\r\n      // function to draw line\r\n      //\r\n      function line(x0,y0,x1,y1) {\r\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\r\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\r\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\r\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\r\n         var row,col\r\n         var idx = ix1-ix0\r\n         var idy = iy1-iy0\r\n         if (Math.abs(idy) > Math.abs(idx)) {\r\n            (idy > 0) ?\r\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\r\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\r\n            for (row = row0; row <= row1; ++row) {\r\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\r\n               image[row*width*4+col*4+0] = 0\r\n               image[row*width*4+col*4+1] = 0\r\n               image[row*width*4+col*4+2] = 0\r\n               image[row*width*4+col*4+3] = 255\r\n               }\r\n            }\r\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\r\n            (idx > 0) ?\r\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\r\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\r\n            for (col = col0; col <= col1; ++col) {\r\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\r\n               image[row*width*4+col*4+0] = 0\r\n               image[row*width*4+col*4+1] = 0\r\n               image[row*width*4+col*4+2] = 0\r\n               image[row*width*4+col*4+3] = 255\r\n               }\r\n            }\r\n         else {\r\n            row = iy0\r\n            col = ix0\r\n            image[row*width*4+col*4+0] = 0\r\n            image[row*width*4+col*4+1] = 0\r\n            image[row*width*4+col*4+2] = 0\r\n            image[row*width*4+col*4+3] = 255\r\n            }\r\n         }\r\n      //\r\n      // get variables\r\n      //\r\n      var height = evt.data.height\r\n      var width = evt.data.width\r\n      var endian = true\r\n      var image = new Uint8ClampedArray(evt.data.image)\r\n      var view = new DataView(evt.data.mesh)\r\n      var triangles = view.getUint32(80,endian)\r\n      //\r\n      // find limits\r\n      //\r\n      var offset = 80+4\r\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\r\n      var xmin = Number.MAX_VALUE\r\n      var xmax = -Number.MAX_VALUE\r\n      var ymin = Number.MAX_VALUE\r\n      var ymax = -Number.MAX_VALUE\r\n      var zmin = Number.MAX_VALUE\r\n      var zmax = -Number.MAX_VALUE\r\n      for (var t = 0; t < triangles; ++t) {\r\n         offset += 3*4\r\n         x0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x0 > xmax) xmax = x0\r\n         if (x0 < xmin) xmin = x0\r\n         y0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y0 > ymax) ymax = y0\r\n         if (y0 < ymin) ymin = y0\r\n         z0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z0 > zmax) zmax = z0\r\n         if (z0 < zmin) zmin = z0\r\n         x1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x1 > xmax) xmax = x1\r\n         if (x1 < xmin) xmin = x1\r\n         y1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y1 > ymax) ymax = y1\r\n         if (y1 < ymin) ymin = y1\r\n         z1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z1 > zmax) zmax = z1\r\n         if (z1 < zmin) zmin = z1\r\n         x2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (x2 > xmax) xmax = x2\r\n         if (x2 < xmin) xmin = x2\r\n         y2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (y2 > ymax) ymax = y2\r\n         if (y2 < ymin) ymin = y2\r\n         z2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         if (z2 > zmax) zmax = z2\r\n         if (z2 < zmin) zmin = z2\r\n         offset += 2\r\n         }\r\n      var dx = xmax-xmin\r\n      var dy = ymax-ymin\r\n      var dz = zmax-zmin\r\n      //\r\n      // draw mesh\r\n      //\r\n      if (dx > dy) {\r\n         var xo = 0\r\n         var yo = height*.5*(1-dy/dx)\r\n         var xw = width-1\r\n         var yh = (width-1)*dy/dx\r\n         }\r\n      else {\r\n         var xo = width*.5*(1-dx/dy)\r\n         var yo = 0\r\n         var xw = (height-1)*dx/dy\r\n         var yh = height-1\r\n         }\r\n      offset = 80+4\r\n      for (var t = 0; t < triangles; ++t) {\r\n         offset += 3*4\r\n         x0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z0 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         x1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z1 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         x2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         y2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         z2 = view.getFloat32(offset,endian)\r\n         offset += 4\r\n         offset += 2\r\n         line(x0,y0,x1,y1)\r\n         line(x1,y1,x2,y2)\r\n         line(x2,y2,x0,y0)\r\n         }\r\n      //\r\n      // return results and close\r\n      //\r\n      self.postMessage({\r\n         dx:dx,dy:dy,dz:dz,\r\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\r\n      self.close()\r\n      })\r\n   }\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"406","left":"746","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7562574507163453\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.4144526456371104\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7562574507163453\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"depth\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32304064019646705\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.7667165137781767\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9325875387173613\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4144526456371104\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.40901053044748015\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}"]}
\ No newline at end of file
diff --git a/programs/machines/ShopBot/mill 2D svg connect b/programs/machines/ShopBot/mill 2D svg connect
index 2e0b5a092ca9d96d0cbfe69b97e9887a2e698c2c..f508cb6078938fc5c651567368d6bbe8167e5d08 100644
--- a/programs/machines/ShopBot/mill 2D svg connect	
+++ b/programs/machines/ShopBot/mill 2D svg connect	
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"168","left":"1908","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"928","left":"2117","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"831","left":"2600","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"462","left":"2986","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"12","left":"1494","inputs":{},"outputs":{}},"0.4793941661670936":{"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":"1225","left":"1239","inputs":{},"outputs":{}},"0.6248369051648597":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"817","left":"752","inputs":{},"outputs":{}},"0.23780413326993044":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the 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 = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"32","left":"2912","inputs":{},"outputs":{}},"0.5857417886002868":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = 100\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         draw_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// draw image\n//\nfunction draw_image() {\n   var dpi = parseFloat(mod.dpi.value)\n   var units = parseFloat(mod.unitstext.value)\n   var width = parseInt(dpi*mod.width/units)\n   var height = parseInt(dpi*mod.height/units)\n   mod.pixels.nodeValue = width+' x '+height+\" (pixels)\"\n   mod.inches.nodeValue = (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n   mod.mm.nodeValue = (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (width > height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-height/width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*height/width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-width/height)\n         var y0 = 0\n         var w = mod.canvas.height*width/height\n         var h = mod.canvas.height\n         }\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.clearRect(0,0,width,height)\n         ctx.drawImage(img,0,0,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(mod.img,x0,y0,w,h)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"64","left":"701","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = false\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"587","left":"1739","inputs":{},"outputs":{}},"0.32734870523599846":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.125\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.1\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.1\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 1\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.reverse.checked = true\n   mod.sort.checked = false\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height \n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value));\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }      \n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"402","left":"1241","inputs":{},"outputs":{}},"0.49036025089153756":{"definition":"//\n// Automatic dogbones for preprocessing images to be machined\n//\n// Sam Calisch\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 = 'dogbone'\n//\n// initialization\n//\nvar init = function() {\n   mod.diameter.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.diameter.value != '')\n            dogbone()\n         }},\n   diameter:{type:'number',\n      event:function(evt){\n         mod.diameter.value = evt.detail\n         if (mod.distances != undefined)\n            dogbone()\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // diameter value\n   //\n   div.appendChild(document.createTextNode('diameter (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         dogbone()\n         })\n      div.appendChild(input)\n      mod.diameter = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// dogbone\n//\nfunction dogbone() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var diameter = parseFloat(mod.diameter.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      diameter:diameter,buffer:mod.distances.buffer})\n   }\n//\n// dogbone worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var diameter = evt.data.diameter\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= 0) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n\n      //pick out ridge points at the right distance\n      var distance_value = (diameter/2.) * (Math.sqrt(2)/2.);\n      var distance_tol = 1; //Math.sqrt(2)/2. ;\n      var r = Math.round(diameter/2);\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            var max_ud = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row)*w+col-1], input[ (h-1-row)*w+col+1]);  //up down\n            var max_lr = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row-1)*w+col], input[ (h-1-row+1)*w+col]); //left right\n            var max_ru = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row-1)*w+col+1], input[ (h-1-row+1)*w+col-1]); //right up\n            var max_rd = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row-1)*w+col-1], input[ (h-1-row+1)*w+col+1]); //right up\n            //if we are local max in at least two directions\n            if( (max_ud+max_lr+max_ru+max_rd) >= 2 && Math.abs(input[ (h-1-row)*w+col]-distance_value) <= distance_tol ) {\n               for(var cx=-r; cx<=r; ++cx){\n                  var yx = Math.ceil(Math.sqrt(r*r-cx*cx));\n                  for(var cy=-yx; cy<=yx; ++cy){\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+0] = 0;\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+1] = 0;\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+2] = 0;\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+3] = 255;\n                  }\n               }\n\n            }     \n         }\n      }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"38","left":"2414","inputs":{},"outputs":{}},"0.8617147326718335":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = 20\n   mod.plungespeed.value = 20\n   mod.jogspeed.value = 75\n   mod.jogheight.value = 5\n   mod.spindlespeed.value = 10000\n   mod.unitsin.checked = true   \n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"1103","left":"783","inputs":{},"outputs":{}},"0.07230598353953022":{"definition":"//\n// nest multiple SVGs into a single SVG\n//\n// Sam Calisch\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  'parameters':{\n      'stock_width':40.,\n      'stock_height':40.,\n      'padding':.25}    //algorithm parameters, and default values\n}\n//\n// name\n//\nvar name = 'nest SVG Array'\n//\n// initialization\n//\nvar init = function() {\n   Object.keys(mod.parameters).forEach( function(k){\n      mod[k].value = mod.parameters[k]; //set default values\n   });\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVGArray:{type:'object',\n      event:function(evt) {\n         mod.svg_array = evt.detail;\n         nest(mod.svg_array);\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         var str = new XMLSerializer().serializeToString(mod.bigview);\n         mods.output(mod,'SVG',str)}},\n   // TODO: make another output for parts that don't fit\n   //Leftovers:{type:'object',\n   //   event:function(){\n   //      mods.output(mod,'Leftovers',mod.leftovers)}}\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n\n   // on-screen drawing canvas\n   var smallview = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");//document.createElement('canvas');\n      smallview.setAttribute('width',mods.ui.canvas);\n      smallview.setAttribute('height',mods.ui.canvas);\n      smallview.setAttribute('preserveAspectRatio','xMinYMin meet');\n      div.appendChild(smallview);\n      mod.smallview = smallview;\n   div.appendChild(document.createElement('br'));\n\n   // off-screen image canvas\n   mod.bigview = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\"); \n\n   //add parameter inputs\n   Object.keys(mod.parameters).forEach(function(p){\n    var textnode = document.createElement('span');\n    textnode.innerHTML = p+': ';\n    textnode.style=\"display: inline-block; width: 80px; font-size: 12px;\";\n    div.appendChild(textnode);\n    var input_text = document.createElement('input');\n    var input_max = document.createElement('input');\n    var input_range = document.createElement('input') //add slider\n\n      //value text\n      input_text.type='text';\n      input_text.size=3;\n      mod[p] = input_text; //set initial minimum slider value\n      div.appendChild(input_text);\n      input_text.addEventListener('blur',function(){\n         input_range.value = (100 * input_text.value / input_max.value);\n         nest(mod.svg_array);\n      });\n\n      //slider\n      input_range.type = 'range'; \n      input_range.min = 0; input_range.max = 100;\n      input_range.value = 50; \n      input_range.style = '-webkit-appearance: none; width: 80px; height: 0px; border: none; margin-top: -4px; margin-left:2px;';\n      input_range.addEventListener('input',function(){\n         input_text.value = input_max.value * input_range.value/100.0;\n         nest(mod.svg_array);\n      });\n      div.appendChild(input_range);\n\n      //max text\n      input_max.type='text';\n      input_max.size=2;\n      input_max.value = 2*mod.parameters[p]; //set initial maximum to twice default value\n      input_max.addEventListener('blur',function(){\n         input_range.value = 100 * input_text.value / input_max.value;\n         input_text.value = Math.min( input_text.value, input_max.value );  \n         nest(mod.svg_array);\n      });\n      div.appendChild(input_max);\n\n    div.appendChild(document.createElement('br'));\n   });\n\n   // view button\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            mod.bigview.setAttribute('width', mod.stock_width.value+\"in\");\n            mod.bigview.setAttribute('height', mod.stock_height.value+\"in\" );\n            mod.bigview.setAttribute('preserveAspectRatio','xMinYMin meet');\n            win.document.body.appendChild(mod.bigview);\n         })\n      div.appendChild(btn)\n      div.appendChild(document.createTextNode(' draw grid?'));\n      var draw_grid = document.createElement('input')\n      mod.draw_grid = draw_grid;\n      draw_grid.type = 'checkbox'\n      draw_grid.name = mod.div.id+'grid'\n      draw_grid.id = mod.div.id+'grid'\n      draw_grid.checked = false\n      draw_grid.addEventListener('change',function(){nest(mod.svg_array);});\n      div.appendChild(draw_grid)\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n\nfunction nest(sw_json){\n   //sw_json is text json exported from soliworks of the form [{partTitle:”Part-1”, thickness:20, count:2, svgArray:[svgf1, svgf2, …],{partTitle:”Part-2”, thickness:20, count:3, svgArray:[svgf3, svgf4, …]\n   //stocksize is an array [width,height] of stock dimensions\n   //we scale so the stock size takes up the %75 of screen\n   //padding is an amount to leave between each piece\n   var unitScale = sw_json[0].unit === \"mm\"?1000:1;\n   var stocksize = [mod.stock_width.value/39.3*unitScale, mod.stock_height.value/39.3*unitScale]; //convert to meters\n   //TODO: handle units more gracefully!\n   var padding = mod.padding.value/39.3*unitScale;\n   var draw_grid = false;\n\n   //make sure first dimension is longer\n   //if (stocksize[1] > stocksize[0]) stocksize = [stocksize[1],stocksize[0]];\n\n   //fit stock width to page\n   var scale = mod.smallview.width/stocksize[0]; \n\n   var partName = sw_json[0]['partName'];\n   var thickness = sw_json[0]['thickness'];\n   var count = sw_json[0]['count'];\n   var svgs = [];\n   //deal with multiple parts from SW\n   for(var i=0; i<sw_json.length; ++i){\n      svgs = svgs.concat(sw_json[i].svgArray);\n   }\n\n   //create SVG for output\n   var svgNS = \"http://www.w3.org/2000/svg\";\n   //var nested = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n   mod.smallview.innerHTML=''; //delete previous children\n   \n   //mod.smallview.setAttribute('width',(stocksize[0])*scale  );\n   //mod.smallview.setAttribute('height',(stocksize[1])*scale  );\n   mod.smallview.setAttribute('viewBox', \"0 0 \"+stocksize[0]+\" \"+stocksize[1]);\n\n   //draw stock outline\n   var stock = document.createElementNS(svgNS,'rect');\n   stock.setAttribute('x', 0); stock.setAttribute('y', 0);\n   stock.setAttribute('width', stocksize[0]); stock.setAttribute('height', stocksize[1]);\n   stock.setAttribute('style', 'fill:rgb(0,0,0);')\n   //stock.setAttribute('class','annotation'); //label for removal on output\n   mod.smallview.appendChild(stock);\n\n   var gs = [] //container for the g elements\n   svgs.forEach(function(svg){\n      var g = document.createElementNS(svgNS,'g');\n      g.innerHTML = svg;\n      var svg_tree = g.firstChild;\n      var vb = svg_tree.getAttribute('viewBox').split(\" \").map(parseFloat);\n      svg_tree.setAttribute('viewBox',(vb[0]-.5*padding)+\" \"+(vb[1]-.5*padding)+\" \"+(vb[2]+.5*padding)+\" \"+(vb[3]+.5*padding));\n      svg_tree.setAttribute('width', vb[2]+padding); svg_tree.setAttribute('height', vb[3]+padding);\n      g.setAttribute('w',vb[2]+padding); //use a foreign tag to bring width and height info along as we tranform \n      g.setAttribute('h',vb[3]+padding); \n      mod.smallview.appendChild(g);\n      gs.push(g);\n   });\n\n   //orient so long axis is horizontal. if this is not the case, rotate\n   gs.forEach(function(g){\n      w = g.getAttribute('w'); h = g.getAttribute('h');\n      if (h>w){\n         g.setAttribute('transform','translate(0,'+w+') rotate(-90)' );\n         g.setAttribute('w',h); g.setAttribute('h',w);\n      } else{\n         g.setAttribute('transform','' );\n      }\n   });\n\n   //then sort by long dimension in descending order\n   gs = gs.sort( function(g1,g2){ return g2.getAttribute('w') -  g1.getAttribute('w')} );\n\n\n   //then place first and calculate the left over rectangles\n   function fill_rect(b1,b2,remaining_shapes){\n      if (mod.draw_grid.checked){\n         node = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n         node.setAttribute('x',b1[0]); node.setAttribute('y',b1[1]);\n         node.setAttribute('width',b2[0]-b1[0]);\n         node.setAttribute('height',b2[1]-b1[1]);\n         node.setAttribute('class','annotation'); //label for removal on output\n         node.setAttribute('style','fill:none;stroke-width:.001;stroke:rgb(0,0,255)');\n         mod.smallview.appendChild(node);\n      }\n\n\n      //fill a rectangle defined by point b1 to point b2 with the first element from remaining_shapes that fits\n      var dx = b2[0]-b1[0]; \n      var dy = b2[1]-b1[1];\n      for(i=0; i<remaining_shapes.length; i++){\n         var gi = remaining_shapes[i];\n         var w = parseFloat(gi.getAttribute('w'));\n         var h = parseFloat(gi.getAttribute('h'));\n         if (w <= dx+.000001 && h <= dy+.00001){ //successfully placed shape i\n            remaining_shapes.splice(i,1); //remove shape i from remaining shapes\n            gi.setAttribute('transform', 'translate('+(b1[0])+','+(b1[1])+') '+gi.getAttribute('transform')); //use base point as transform\n            fill_rect([b1[0],b1[1]+h],[b1[0]+w,b2[1]],remaining_shapes); //prioritize filling lower rectangle\n            fill_rect([b1[0]+w,b1[1]],b2,remaining_shapes);           //then fill right rectangle\n            break; //break out of for loop\n         }\n      }\n\n   }\n   fill_rect([.5*padding,.5*padding],[stocksize[0]-.5*padding,stocksize[1]-.5*padding],gs);\n      \n   //HACK: don't show parts that don't fit\n   gs.forEach(function(g){\n      g.innerHTML = ''; //delete!\n   });   \n\n\n   //create bigview svg from smallview svg\n   mod.bigview.setAttribute( 'viewBox', mod.smallview.getAttribute('viewBox') );\n   mod.bigview.innerHTML = mod.smallview.innerHTML;\n   mod.bigview.setAttribute('width', mod.stock_width.value+\"in\");\n   mod.bigview.setAttribute('height', mod.stock_height.value+\"in\" );\n\n   //output events\n   //mod.leftovers = [];\n   outputs.SVG.event()\n\n\n   /*\n\n   //highlight parts that didn't fit\n   gs.forEach(function(g){\n      var svg = g.firstChild;\n      var style = svg.firstChild.getAttribute('style');\n      svg.firstChild.setAttribute('style',style+'stroke-width:'+.002*stocksize[0]+'; stroke:rgb(255,0,0)')\n   });\n   //nest parts that don't fit, note: this could just be done on another page.\n   if (gs.length > 0){\n      fill_rect([.5*padding,.5*padding],[stocksize[0]-.5*padding,stocksize[1]-.5*padding],gs);\n   }\n   */\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":"650","left":"335","inputs":{},"outputs":{}},"0.10390878290633299":{"definition":"//\n// string variables\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   'sw_array':[{\"partName\":\"rwanda-stool.SLDPRT\",\"unit\":\"meter\",\"thickness\":0.01905,\"count\":1,\"svgArray\":[\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"1127.67015523439\\\" height=\\\"989.826899907394\\\" viewBox=\\\"0 0 0.281917538808599 0.247456724976849\\\" style=\\\"background:black\\\"><path d=\\\" M  0.280917538808598,0.0359877248472174 A 0.35,0.35 0 0,0 0.161236292032361,0.243281724976848 L  0.155737030718329,0.246456724976848 L  0.150237769404297,0.246456724976848 L  0.150237769404298,0.182920175324464 L  0.149983769404299,0.182920175324464 L  0.149983769404299,0.118489816608022 L  0.130933769404299,0.118489816608022 L  0.130933769404299,0.182920175324464 L  0.130679769404298,0.182920175324464 L  0.130679769404297,0.246456724976849 L  0.125180508090266,0.246456724976849 L  0.119681246776236,0.243281724976848 A 0.35,0.35 0 0,0 0,0.0359877248472167 L  2.22044604925031E-16,0.0296377248472162 L  0.00274963065701597,0.024875224847216 L  0.0577738967247917,0.0566434996734087 L  0.0675528967247919,0.0397057748261927 L  0.012528630657016,0.00793749999999993 L  0.0152782613140317,0.0031749999999999 L  0.0207775226280628,0 A 0.35,0.35 0 0,0 0.260140016180536,2.5951463200613E-15 L  0.265639277494567,0.00317500000000245 L  0.268388908151583,0.00793750000000276 L  0.213364642083807,0.0397057748261942 L  0.223143642083807,0.0566434996734103 L  0.278167908151583,0.0248752248472187 L  0.280917538808599,0.0296377248472181 L  0.280917538808598,0.0359877248472174\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.281417538808599 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"1324.8\\\" height=\\\"1324.8\\\" viewBox=\\\"0 0 0.3312 0.3312\\\" style=\\\"background:black\\\"><path d=\\\" M  0.208704299650783,0.151216862423607 L  0.198925299650783,0.134279137576391 L  0.181987574803567,0.14405813757639 L  0.191766574803567,0.160995862423607 L  0.208704299650783,0.151216862423607 M  0.257757710571941,0.122895862423607 L  0.274695435419157,0.113116862423607 L  0.264916435419157,0.0961791375763911 L  0.247978710571941,0.105958137576391 L  0.257757710571941,0.122895862423607 M  0.23192086753497,0.115229137576391 L  0.214983142687754,0.125008137576391 L  0.224762142687754,0.141945862423607 L  0.24169986753497,0.132166862423607 L  0.23192086753497,0.115229137576391 M  0.0822212894280657,0.10595813757639 L  0.0652835645808497,0.0961791375763897 L  0.0555045645808495,0.113116862423606 L  0.0724422894280656,0.122895862423606 L  0.0822212894280657,0.10595813757639 M  0.115216857312253,0.12500813757639 L  0.0982791324650367,0.11522913757639 L  0.0885001324650366,0.132166862423606 L  0.105437857312253,0.141945862423606 L  0.115216857312253,0.12500813757639 M  0.14821242519644,0.14405813757639 L  0.131274700349224,0.13427913757639 L  0.121495700349224,0.151216862423606 L  0.13843342519644,0.160995862423606 L  0.14821242519644,0.14405813757639 M  0.155321000000003,0.190245999999998 L  0.155321000000003,0.209803999999998 L  0.174879000000003,0.209803999999998 L  0.174879000000003,0.190245999999998 L  0.155321000000003,0.190245999999998 M  0.155321000000003,0.228345999999998 L  0.155321000000003,0.247903999999998 L  0.174879000000003,0.247903999999998 L  0.174879000000003,0.228345999999998 L  0.155321000000003,0.228345999999998 M  0.155321000000003,0.266445999999998 L  0.155321000000003,0.286003999999998 L  0.174879000000003,0.286003999999998 L  0.174879000000003,0.266445999999998 L  0.155321000000003,0.266445999999998 M  0.3302,0.1651 A 0.1651,0.1651 0 1,1 0,0.1651 A 0.1651,0.1651 0 1,1 0.3302,0.1651\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.3307 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"1098.95817229398\\\" height=\\\"952.261593287967\\\" viewBox=\\\"0 0 0.274739543073496 0.238065398321992\\\" style=\\\"background:black\\\"><path d=\\\" M  0.273739543073496,0.220134601678009 L  0.263964543073497,0.237065398321992 A 0.45,0.45 0 0,0 0.00977499999999865,0.237065398321992 L  0,0.220134601678008 A 0.45,0.45 0 0,0 0.127094771536749,2.77555756156289E-17 L  0.146644771536747,0 A 0.45,0.45 0 0,0 0.273739543073496,0.220134601678009 M  0.197751436852561,0.198841862423608 L  0.220188423013809,0.211795862423607 L  0.229967423013809,0.194858137576391 L  0.207530436852561,0.181904137576391 L  0.197751436852561,0.198841862423608 M  0.127090771536748,0.0505460000000001 L  0.127090771536748,0.0764540000000001 L  0.146648771536748,0.0764540000000001 L  0.146648771536748,0.0505460000000001 L  0.127090771536748,0.0505460000000001 M  0.127090771536748,0.101346 L  0.127090771536748,0.127254 L  0.146648771536748,0.127254 L  0.146648771536748,0.101346 L  0.127090771536748,0.101346 M  0.0535511200596879,0.211795862423609 L  0.0759881062209351,0.198841862423608 L  0.0662091062209351,0.181904137576392 L  0.0437721200596878,0.194858137576393 L  0.0535511200596879,0.211795862423609 M  0.0975452105719373,0.186395862423608 L  0.119982196733185,0.173441862423608 L  0.110203196733184,0.156504137576392 L  0.0877662105719372,0.169458137576392 L  0.0975452105719373,0.186395862423608 M  0.185973332501559,0.169458137576392 L  0.163536346340311,0.156504137576392 L  0.153757346340312,0.173441862423608 L  0.176194332501559,0.186395862423608 L  0.185973332501559,0.169458137576392\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.274239543073496 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"715.2\\\" height=\\\"2442.4\\\" viewBox=\\\"0 0 0.1788 0.6106\\\" style=\\\"background:black\\\"><path d=\\\" M  0.0644303587164418,0.3302 L  0,0.3302 L  3.81639164714898E-17,0.01905 L  0.0254,0.01905 L  0.0254,0.038354 L  0.0508,0.038354 L  0.0508,0.01905 L  0.0762,0.01905 L  0.0762,0.038354 L  0.127,0.038354 L  0.127,0 L  0.17145,0 A 0.00635,0.00635 0 0,1 0.1778,0.00635 L  0.1778,0.038354 A 4,4 0 0,0 0.10795,0.590296000000001 L  0.0952500000000001,0.590296 L  0.09525,0.6096 L  0.0762,0.6096 L  0.0762,0.590296 L  0.05715,0.590296 L  0.05715,0.6096 L  0.0381,0.6096 L  0.0381,0.590296 L  0.01905,0.590296 L  0.01905,0.6096 L  0,0.6096 L  0,0.34925 L  0.0644303587164418,0.34925 L  0.0644303587164418,0.3302 M  0.0381,0.545846 A 0.00634999999999998,0.00634999999999998 0 0,0 0.0444500000000001,0.552196 L  0.066152723772689,0.552196 A 0.00635000000000026,0.00635000000000026 0 0,0 0.0724886137147865,0.546269082310705 A 4.0381,4.0381 0 0,1 0.0855169733792192,0.394361959562247 A 0.00635000000000038,0.00635000000000038 0 0,0 0.0792015708158069,0.38735 L  0.0444500000000001,0.38735 A 0.00635000000000001,0.00635000000000001 0 0,0 0.0381,0.3937 L  0.0381,0.545846 M  0.0381,0.0828040000000001 L  0.0381,0.28575 A 0.00635000000000002,0.00635000000000002 0 0,0 0.0444500000000001,0.2921 L  0.0919853797681748,0.2921 A 0.00634999999999996,0.00634999999999996 0 0,0 0.0982807110766947,0.286581446760754 A 4.0381,4.0381 0 0,1 0.130298417149589,0.0839540826939461 A 0.00635000000000012,0.00635000000000012 0 0,0 0.12405343437963,0.076454 L  0.0444500000000001,0.076454 A 0.00634999999999999,0.00634999999999999 0 0,0 0.0381,0.0828040000000001\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.1783 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"715.2\\\" height=\\\"2442.4\\\" viewBox=\\\"0 0 0.1788 0.6106\\\" style=\\\"background:black\\\"><path d=\\\" M  0.0644303587164418,0.3302 L  0,0.3302 L  3.81639164714898E-17,0.01905 L  0.0254,0.01905 L  0.0254,0.038354 L  0.0508,0.038354 L  0.0508,0.01905 L  0.0762000000000001,0.01905 L  0.0762000000000001,0.038354 L  0.127,0.038354 L  0.127,0 L  0.17145,0 A 0.00635,0.00635 0 0,1 0.1778,0.00635 L  0.1778,0.038354 A 4,4 0 0,0 0.10795,0.590296000000001 L  0.0952500000000001,0.590296 L  0.09525,0.6096 L  0.0762,0.6096 L  0.0762,0.590296 L  0.05715,0.590296 L  0.05715,0.6096 L  0.0381,0.6096 L  0.0381,0.590296 L  0.01905,0.590296 L  0.01905,0.6096 L  0,0.6096 L  0,0.34925 L  0.0644303587164418,0.34925 L  0.0644303587164418,0.3302 M  0.0381,0.545846 A 0.00634999999999998,0.00634999999999998 0 0,0 0.0444500000000001,0.552196 L  0.066152723772689,0.552196 A 0.00635000000000026,0.00635000000000026 0 0,0 0.0724886137147865,0.546269082310705 A 4.0381,4.0381 0 0,1 0.0855169733792192,0.394361959562247 A 0.00635000000000038,0.00635000000000038 0 0,0 0.0792015708158069,0.38735 L  0.0444500000000001,0.38735 A 0.00635000000000001,0.00635000000000001 0 0,0 0.0381,0.3937 L  0.0381,0.545846 M  0.0381,0.0828040000000001 L  0.0381,0.28575 A 0.00635000000000002,0.00635000000000002 0 0,0 0.0444500000000001,0.2921 L  0.0919853797681748,0.2921 A 0.00634999999999996,0.00634999999999996 0 0,0 0.0982807110766947,0.286581446760754 A 4.0381,4.0381 0 0,1 0.130298417149589,0.0839540826939461 A 0.00635000000000012,0.00635000000000012 0 0,0 0.12405343437963,0.076454 L  0.0444500000000001,0.076454 A 0.00634999999999999,0.00634999999999999 0 0,0 0.0381,0.0828040000000001\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.1783 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"715.2\\\" height=\\\"2442.4\\\" viewBox=\\\"0 0 0.1788 0.6106\\\" style=\\\"background:black\\\"><path d=\\\" M  0.113369641283558,0.2794 L  0.1778,0.2794 L  0.1778,0.59055 L  0.1524,0.59055 L  0.1524,0.571246 L  0.127,0.571246 L  0.127,0.59055 L  0.1016,0.59055 L  0.1016,0.571246 L  0.0508,0.571246 L  0.0508,0.6096 L  0.00635000000000002,0.6096 A 0.00635,0.00635 0 0,1 0,0.60325 L  0,0.571246 A 4,4 0 0,0 0.0698499999999999,0.0193039999999987 L  0.08255,0.019304 L  0.0825500000000001,1.11022302462516E-16 L  0.1016,0 L  0.1016,0.019304 L  0.12065,0.0193039999999999 L  0.12065,1.11022302462516E-16 L  0.1397,1.11022302462516E-16 L  0.1397,0.0193039999999999 L  0.15875,0.019304 L  0.15875,0 L  0.1778,1.11022302462516E-16 L  0.1778,0.26035 L  0.113369641283558,0.26035 L  0.113369641283558,0.2794 M  0.1397,0.0637540000000001 A 0.00634999999999998,0.00634999999999998 0 0,0 0.13335,0.057404 L  0.111647276227311,0.0574039999999997 A 0.00635000000000026,0.00635000000000026 0 0,0 0.105311386285214,0.0633309176892949 A 4.0381,4.0381 0 0,1 0.0922830266207809,0.215238040437753 A 0.00635000000000038,0.00635000000000038 0 0,0 0.0985984291841932,0.22225 L  0.13335,0.22225 A 0.00635000000000001,0.00635000000000001 0 0,0 0.1397,0.2159 L  0.1397,0.0637540000000001 M  0.1397,0.526796 L  0.1397,0.32385 A 0.00635000000000002,0.00635000000000002 0 0,0 0.13335,0.3175 L  0.0858146202318253,0.3175 A 0.00634999999999996,0.00634999999999996 0 0,0 0.0795192889233054,0.323018553239246 A 4.0381,4.0381 0 0,1 0.047501582850411,0.525645917306054 A 0.00635000000000012,0.00635000000000012 0 0,0 0.0537465656203701,0.533146 L  0.13335,0.533146 A 0.00634999999999999,0.00634999999999999 0 0,0 0.1397,0.526796\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.1783 0.0005), scale(-1, 1)\\\" /></svg>\"]}]\n}\n//\n// name\n//\nvar name = 'sw output'\n//\n// initialization\n//\nvar init = function() {   }\n//\n// inputs\n//\nvar inputs = {\n      }\n//\n// outputs\n//\nvar outputs = {\n   sw_array:{type:'object',\n      event:function(){\n         mods.output(mod,'sw_array',mod.sw_array )}}\n      }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n\n   var btn = document.createElement('button');\n      btn.style.padding = mods.ui.padding;\n      btn.style.margin = 1;\n      btn.appendChild(document.createTextNode('send'));\n      btn.addEventListener('click',function(){\n         outputs.sw_array.event();\n      });\n      div.appendChild(btn);\n\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\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":"469","left":"22","inputs":{},"outputs":{}},"0.14150626327820492":{"definition":"//\r\n// ExtractFaces module extracts top faces of the same thickness from Tools/FabLab Connect command of SolidWorks products\r\n// \r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2016\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'ExtractFaces connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n   mod.port = getParameterByName('swPort') || '80'\r\n   mod.socket = 0\r\n   mod.thickness.value = 0.75\r\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   SVGArray:{type:'object',\r\n      event:function(data){\r\n          mods.output(mod, 'SVGArray', JSON.parse(data))\r\n      }\r\n   }\r\n}\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   div.appendChild(document.createTextNode('server:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n   input.type = 'text'\r\n   input.size = 12\r\n   div.appendChild(input)\r\n   mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('open'))\r\n   btn.addEventListener('click', function () {\r\n       socket_open()\r\n   })\r\n   div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('close'))\r\n   btn.addEventListener('click', function () {\r\n       socket_close()\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('thickness: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.thickness = input\r\n   div.appendChild(document.createTextNode('(inch)'))\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Extract SVGs'))\r\n      btn.addEventListener('click',function() {\r\n         extract_SVGs()\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href;\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url);\r\n    if (!results) return null;\r\n    if (!results[2]) return '';\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\r\n}\r\n\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address+':'+mod.port\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n       mod.status.value = \"opened\"\r\n       var connect = {}\r\n       connect.modCmd = 'connect'\r\n       connect.owner = getParameterByName('swOwner')\r\n       connect.id = getParameterByName('swID')\r\n       socket_send(JSON.stringify(connect))\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = \"receive\"\r\n      var swData = JSON.parse(event.data);\r\n      if (swData.swType === \"FaceSVGArray\") {\r\n          outputs.SVGArray.event(JSON.stringify(swData.data))\r\n      }\r\n   }\r\n   mod.socket.onclose = function (event) {\r\n       mod.status.value = \"connection closed\"\r\n   }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"closed\"\r\n   mod.socket = 0\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != 0) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction extract_SVGs() {\r\n   var modcmd = new Object;\r\n   modcmd.modCmd = \"AutoExtractFaces\";\r\n   modcmd.thickness = Number(mod.thickness.value * 25.4); // inch to mm\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\n\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"96","left":"192","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.23780413326993044\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5857417886002868\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5857417886002868\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6248369051648597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.49036025089153756\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.23780413326993044\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.49036025089153756\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"diameter\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.49036025089153756\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"diameter\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6248369051648597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8617147326718335\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8617147326718335\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.07230598353953022\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5857417886002868\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.14150626327820492\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVGArray\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07230598353953022\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVGArray\\\"}\"}"]}
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"340","left":"2454","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1100","left":"2663","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1003","left":"3146","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"634","left":"3532","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = '0.5'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"184","left":"2040","inputs":{},"outputs":{}},"0.4793941661670936":{"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":"1397","left":"1785","inputs":{},"outputs":{}},"0.6248369051648597":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"989","left":"1298","inputs":{},"outputs":{}},"0.23780413326993044":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the 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 = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"204","left":"3458","inputs":{},"outputs":{}},"0.5857417886002868":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '100'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         draw_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         draw_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// draw image\n//\nfunction draw_image() {\n   var dpi = parseFloat(mod.dpi.value)\n   var units = parseFloat(mod.unitstext.value)\n   var width = parseInt(dpi*mod.width/units)\n   var height = parseInt(dpi*mod.height/units)\n   mod.pixels.nodeValue = width+' x '+height+\" (pixels)\"\n   mod.inches.nodeValue = (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n   mod.mm.nodeValue = (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (width > height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-height/width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*height/width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-width/height)\n         var y0 = 0\n         var w = mod.canvas.height*width/height\n         var h = mod.canvas.height\n         }\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.clearRect(0,0,width,height)\n         ctx.drawImage(img,0,0,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(mod.img,x0,y0,w,h)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"236","left":"1247","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   mod.sort.checked = false\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"759","left":"2285","inputs":{},"outputs":{}},"0.32734870523599846":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.125'\n   mod.dia_mm.value = '3.175'\n   mod.cut_in.value = '0.1'\n   mod.cut_mm.value = '2.54'\n   mod.max_in.value = '0.1'\n   mod.max_mm.value = '2.54'\n   mod.number.value = '1'\n   mod.stepover.value = '0.5'\n   mod.merge.value = '1'\n   mod.reverse.checked = true\n   mod.sort.checked = false\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height \n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value));\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }      \n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"574","left":"1787","inputs":{},"outputs":{}},"0.49036025089153756":{"definition":"//\n// Automatic dogbones for preprocessing images to be machined\n//\n// Sam Calisch\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 = 'dogbone'\n//\n// initialization\n//\nvar init = function() {\n   mod.diameter.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.diameter.value != '')\n            dogbone()\n         }},\n   diameter:{type:'number',\n      event:function(evt){\n         mod.diameter.value = evt.detail\n         if (mod.distances != undefined)\n            dogbone()\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // diameter value\n   //\n   div.appendChild(document.createTextNode('diameter (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         dogbone()\n         })\n      div.appendChild(input)\n      mod.diameter = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// dogbone\n//\nfunction dogbone() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var diameter = parseFloat(mod.diameter.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      diameter:diameter,buffer:mod.distances.buffer})\n   }\n//\n// dogbone worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var diameter = evt.data.diameter\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= 0) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n\n      //pick out ridge points at the right distance\n      var distance_value = (diameter/2.) * (Math.sqrt(2)/2.);\n      var distance_tol = 1; //Math.sqrt(2)/2. ;\n      var r = Math.round(diameter/2);\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            var max_ud = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row)*w+col-1], input[ (h-1-row)*w+col+1]);  //up down\n            var max_lr = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row-1)*w+col], input[ (h-1-row+1)*w+col]); //left right\n            var max_ru = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row-1)*w+col+1], input[ (h-1-row+1)*w+col-1]); //right up\n            var max_rd = input[ (h-1-row)*w+col ] >= Math.max(input[ (h-1-row-1)*w+col-1], input[ (h-1-row+1)*w+col+1]); //right up\n            //if we are local max in at least two directions\n            if( (max_ud+max_lr+max_ru+max_rd) >= 2 && Math.abs(input[ (h-1-row)*w+col]-distance_value) <= distance_tol ) {\n               for(var cx=-r; cx<=r; ++cx){\n                  var yx = Math.ceil(Math.sqrt(r*r-cx*cx));\n                  for(var cy=-yx; cy<=yx; ++cy){\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+0] = 0;\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+1] = 0;\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+2] = 0;\n                     output[(h-1-(row+cx))*w*4+(col+cy)*4+3] = 255;\n                  }\n               }\n\n            }     \n         }\n      }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"210","left":"2960","inputs":{},"outputs":{}},"0.8617147326718335":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '20'\n   mod.plungespeed.value = '20'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '5'\n   mod.spindlespeed.value = '10000'\n   mod.unitsin.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"1275","left":"1329","inputs":{},"outputs":{}},"0.10390878290633299":{"definition":"//\n// string variables\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   'sw_array':[{\"partName\":\"rwanda-stool.SLDPRT\",\"unit\":\"meter\",\"thickness\":0.01905,\"count\":1,\"svgArray\":[\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"1127.67015523439\\\" height=\\\"989.826899907394\\\" viewBox=\\\"0 0 0.281917538808599 0.247456724976849\\\" style=\\\"background:black\\\"><path d=\\\" M  0.280917538808598,0.0359877248472174 A 0.35,0.35 0 0,0 0.161236292032361,0.243281724976848 L  0.155737030718329,0.246456724976848 L  0.150237769404297,0.246456724976848 L  0.150237769404298,0.182920175324464 L  0.149983769404299,0.182920175324464 L  0.149983769404299,0.118489816608022 L  0.130933769404299,0.118489816608022 L  0.130933769404299,0.182920175324464 L  0.130679769404298,0.182920175324464 L  0.130679769404297,0.246456724976849 L  0.125180508090266,0.246456724976849 L  0.119681246776236,0.243281724976848 A 0.35,0.35 0 0,0 0,0.0359877248472167 L  2.22044604925031E-16,0.0296377248472162 L  0.00274963065701597,0.024875224847216 L  0.0577738967247917,0.0566434996734087 L  0.0675528967247919,0.0397057748261927 L  0.012528630657016,0.00793749999999993 L  0.0152782613140317,0.0031749999999999 L  0.0207775226280628,0 A 0.35,0.35 0 0,0 0.260140016180536,2.5951463200613E-15 L  0.265639277494567,0.00317500000000245 L  0.268388908151583,0.00793750000000276 L  0.213364642083807,0.0397057748261942 L  0.223143642083807,0.0566434996734103 L  0.278167908151583,0.0248752248472187 L  0.280917538808599,0.0296377248472181 L  0.280917538808598,0.0359877248472174\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.281417538808599 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"1324.8\\\" height=\\\"1324.8\\\" viewBox=\\\"0 0 0.3312 0.3312\\\" style=\\\"background:black\\\"><path d=\\\" M  0.208704299650783,0.151216862423607 L  0.198925299650783,0.134279137576391 L  0.181987574803567,0.14405813757639 L  0.191766574803567,0.160995862423607 L  0.208704299650783,0.151216862423607 M  0.257757710571941,0.122895862423607 L  0.274695435419157,0.113116862423607 L  0.264916435419157,0.0961791375763911 L  0.247978710571941,0.105958137576391 L  0.257757710571941,0.122895862423607 M  0.23192086753497,0.115229137576391 L  0.214983142687754,0.125008137576391 L  0.224762142687754,0.141945862423607 L  0.24169986753497,0.132166862423607 L  0.23192086753497,0.115229137576391 M  0.0822212894280657,0.10595813757639 L  0.0652835645808497,0.0961791375763897 L  0.0555045645808495,0.113116862423606 L  0.0724422894280656,0.122895862423606 L  0.0822212894280657,0.10595813757639 M  0.115216857312253,0.12500813757639 L  0.0982791324650367,0.11522913757639 L  0.0885001324650366,0.132166862423606 L  0.105437857312253,0.141945862423606 L  0.115216857312253,0.12500813757639 M  0.14821242519644,0.14405813757639 L  0.131274700349224,0.13427913757639 L  0.121495700349224,0.151216862423606 L  0.13843342519644,0.160995862423606 L  0.14821242519644,0.14405813757639 M  0.155321000000003,0.190245999999998 L  0.155321000000003,0.209803999999998 L  0.174879000000003,0.209803999999998 L  0.174879000000003,0.190245999999998 L  0.155321000000003,0.190245999999998 M  0.155321000000003,0.228345999999998 L  0.155321000000003,0.247903999999998 L  0.174879000000003,0.247903999999998 L  0.174879000000003,0.228345999999998 L  0.155321000000003,0.228345999999998 M  0.155321000000003,0.266445999999998 L  0.155321000000003,0.286003999999998 L  0.174879000000003,0.286003999999998 L  0.174879000000003,0.266445999999998 L  0.155321000000003,0.266445999999998 M  0.3302,0.1651 A 0.1651,0.1651 0 1,1 0,0.1651 A 0.1651,0.1651 0 1,1 0.3302,0.1651\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.3307 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"1098.95817229398\\\" height=\\\"952.261593287967\\\" viewBox=\\\"0 0 0.274739543073496 0.238065398321992\\\" style=\\\"background:black\\\"><path d=\\\" M  0.273739543073496,0.220134601678009 L  0.263964543073497,0.237065398321992 A 0.45,0.45 0 0,0 0.00977499999999865,0.237065398321992 L  0,0.220134601678008 A 0.45,0.45 0 0,0 0.127094771536749,2.77555756156289E-17 L  0.146644771536747,0 A 0.45,0.45 0 0,0 0.273739543073496,0.220134601678009 M  0.197751436852561,0.198841862423608 L  0.220188423013809,0.211795862423607 L  0.229967423013809,0.194858137576391 L  0.207530436852561,0.181904137576391 L  0.197751436852561,0.198841862423608 M  0.127090771536748,0.0505460000000001 L  0.127090771536748,0.0764540000000001 L  0.146648771536748,0.0764540000000001 L  0.146648771536748,0.0505460000000001 L  0.127090771536748,0.0505460000000001 M  0.127090771536748,0.101346 L  0.127090771536748,0.127254 L  0.146648771536748,0.127254 L  0.146648771536748,0.101346 L  0.127090771536748,0.101346 M  0.0535511200596879,0.211795862423609 L  0.0759881062209351,0.198841862423608 L  0.0662091062209351,0.181904137576392 L  0.0437721200596878,0.194858137576393 L  0.0535511200596879,0.211795862423609 M  0.0975452105719373,0.186395862423608 L  0.119982196733185,0.173441862423608 L  0.110203196733184,0.156504137576392 L  0.0877662105719372,0.169458137576392 L  0.0975452105719373,0.186395862423608 M  0.185973332501559,0.169458137576392 L  0.163536346340311,0.156504137576392 L  0.153757346340312,0.173441862423608 L  0.176194332501559,0.186395862423608 L  0.185973332501559,0.169458137576392\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.274239543073496 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"715.2\\\" height=\\\"2442.4\\\" viewBox=\\\"0 0 0.1788 0.6106\\\" style=\\\"background:black\\\"><path d=\\\" M  0.0644303587164418,0.3302 L  0,0.3302 L  3.81639164714898E-17,0.01905 L  0.0254,0.01905 L  0.0254,0.038354 L  0.0508,0.038354 L  0.0508,0.01905 L  0.0762,0.01905 L  0.0762,0.038354 L  0.127,0.038354 L  0.127,0 L  0.17145,0 A 0.00635,0.00635 0 0,1 0.1778,0.00635 L  0.1778,0.038354 A 4,4 0 0,0 0.10795,0.590296000000001 L  0.0952500000000001,0.590296 L  0.09525,0.6096 L  0.0762,0.6096 L  0.0762,0.590296 L  0.05715,0.590296 L  0.05715,0.6096 L  0.0381,0.6096 L  0.0381,0.590296 L  0.01905,0.590296 L  0.01905,0.6096 L  0,0.6096 L  0,0.34925 L  0.0644303587164418,0.34925 L  0.0644303587164418,0.3302 M  0.0381,0.545846 A 0.00634999999999998,0.00634999999999998 0 0,0 0.0444500000000001,0.552196 L  0.066152723772689,0.552196 A 0.00635000000000026,0.00635000000000026 0 0,0 0.0724886137147865,0.546269082310705 A 4.0381,4.0381 0 0,1 0.0855169733792192,0.394361959562247 A 0.00635000000000038,0.00635000000000038 0 0,0 0.0792015708158069,0.38735 L  0.0444500000000001,0.38735 A 0.00635000000000001,0.00635000000000001 0 0,0 0.0381,0.3937 L  0.0381,0.545846 M  0.0381,0.0828040000000001 L  0.0381,0.28575 A 0.00635000000000002,0.00635000000000002 0 0,0 0.0444500000000001,0.2921 L  0.0919853797681748,0.2921 A 0.00634999999999996,0.00634999999999996 0 0,0 0.0982807110766947,0.286581446760754 A 4.0381,4.0381 0 0,1 0.130298417149589,0.0839540826939461 A 0.00635000000000012,0.00635000000000012 0 0,0 0.12405343437963,0.076454 L  0.0444500000000001,0.076454 A 0.00634999999999999,0.00634999999999999 0 0,0 0.0381,0.0828040000000001\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.1783 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"715.2\\\" height=\\\"2442.4\\\" viewBox=\\\"0 0 0.1788 0.6106\\\" style=\\\"background:black\\\"><path d=\\\" M  0.0644303587164418,0.3302 L  0,0.3302 L  3.81639164714898E-17,0.01905 L  0.0254,0.01905 L  0.0254,0.038354 L  0.0508,0.038354 L  0.0508,0.01905 L  0.0762000000000001,0.01905 L  0.0762000000000001,0.038354 L  0.127,0.038354 L  0.127,0 L  0.17145,0 A 0.00635,0.00635 0 0,1 0.1778,0.00635 L  0.1778,0.038354 A 4,4 0 0,0 0.10795,0.590296000000001 L  0.0952500000000001,0.590296 L  0.09525,0.6096 L  0.0762,0.6096 L  0.0762,0.590296 L  0.05715,0.590296 L  0.05715,0.6096 L  0.0381,0.6096 L  0.0381,0.590296 L  0.01905,0.590296 L  0.01905,0.6096 L  0,0.6096 L  0,0.34925 L  0.0644303587164418,0.34925 L  0.0644303587164418,0.3302 M  0.0381,0.545846 A 0.00634999999999998,0.00634999999999998 0 0,0 0.0444500000000001,0.552196 L  0.066152723772689,0.552196 A 0.00635000000000026,0.00635000000000026 0 0,0 0.0724886137147865,0.546269082310705 A 4.0381,4.0381 0 0,1 0.0855169733792192,0.394361959562247 A 0.00635000000000038,0.00635000000000038 0 0,0 0.0792015708158069,0.38735 L  0.0444500000000001,0.38735 A 0.00635000000000001,0.00635000000000001 0 0,0 0.0381,0.3937 L  0.0381,0.545846 M  0.0381,0.0828040000000001 L  0.0381,0.28575 A 0.00635000000000002,0.00635000000000002 0 0,0 0.0444500000000001,0.2921 L  0.0919853797681748,0.2921 A 0.00634999999999996,0.00634999999999996 0 0,0 0.0982807110766947,0.286581446760754 A 4.0381,4.0381 0 0,1 0.130298417149589,0.0839540826939461 A 0.00635000000000012,0.00635000000000012 0 0,0 0.12405343437963,0.076454 L  0.0444500000000001,0.076454 A 0.00634999999999999,0.00634999999999999 0 0,0 0.0381,0.0828040000000001\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.1783 0.0005), scale(-1, 1)\\\" /></svg>\", \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" width=\\\"715.2\\\" height=\\\"2442.4\\\" viewBox=\\\"0 0 0.1788 0.6106\\\" style=\\\"background:black\\\"><path d=\\\" M  0.113369641283558,0.2794 L  0.1778,0.2794 L  0.1778,0.59055 L  0.1524,0.59055 L  0.1524,0.571246 L  0.127,0.571246 L  0.127,0.59055 L  0.1016,0.59055 L  0.1016,0.571246 L  0.0508,0.571246 L  0.0508,0.6096 L  0.00635000000000002,0.6096 A 0.00635,0.00635 0 0,1 0,0.60325 L  0,0.571246 A 4,4 0 0,0 0.0698499999999999,0.0193039999999987 L  0.08255,0.019304 L  0.0825500000000001,1.11022302462516E-16 L  0.1016,0 L  0.1016,0.019304 L  0.12065,0.0193039999999999 L  0.12065,1.11022302462516E-16 L  0.1397,1.11022302462516E-16 L  0.1397,0.0193039999999999 L  0.15875,0.019304 L  0.15875,0 L  0.1778,1.11022302462516E-16 L  0.1778,0.26035 L  0.113369641283558,0.26035 L  0.113369641283558,0.2794 M  0.1397,0.0637540000000001 A 0.00634999999999998,0.00634999999999998 0 0,0 0.13335,0.057404 L  0.111647276227311,0.0574039999999997 A 0.00635000000000026,0.00635000000000026 0 0,0 0.105311386285214,0.0633309176892949 A 4.0381,4.0381 0 0,1 0.0922830266207809,0.215238040437753 A 0.00635000000000038,0.00635000000000038 0 0,0 0.0985984291841932,0.22225 L  0.13335,0.22225 A 0.00635000000000001,0.00635000000000001 0 0,0 0.1397,0.2159 L  0.1397,0.0637540000000001 M  0.1397,0.526796 L  0.1397,0.32385 A 0.00635000000000002,0.00635000000000002 0 0,0 0.13335,0.3175 L  0.0858146202318253,0.3175 A 0.00634999999999996,0.00634999999999996 0 0,0 0.0795192889233054,0.323018553239246 A 4.0381,4.0381 0 0,1 0.047501582850411,0.525645917306054 A 0.00635000000000012,0.00635000000000012 0 0,0 0.0537465656203701,0.533146 L  0.13335,0.533146 A 0.00634999999999999,0.00634999999999999 0 0,0 0.1397,0.526796\\\" style=\\\"fill:#ffffff;fill-rule:evenodd;\\\" transform=\\\"translate(0.1783 0.0005), scale(-1, 1)\\\" /></svg>\"]}]\n}\n//\n// name\n//\nvar name = 'sw output'\n//\n// initialization\n//\nvar init = function() {   }\n//\n// inputs\n//\nvar inputs = {\n      }\n//\n// outputs\n//\nvar outputs = {\n   sw_array:{type:'object',\n      event:function(){\n         mods.output(mod,'sw_array',mod.sw_array )}}\n      }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n\n   var btn = document.createElement('button');\n      btn.style.padding = mods.ui.padding;\n      btn.style.margin = 1;\n      btn.appendChild(document.createTextNode('send'));\n      btn.addEventListener('click',function(){\n         outputs.sw_array.event();\n      });\n      div.appendChild(btn);\n\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\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":"641","left":"568","inputs":{},"outputs":{}},"0.14150626327820492":{"definition":"//\r\n// ExtractFaces module extracts top faces of the same thickness from Tools/FabLab Connect command of SolidWorks products\r\n// \r\n// Shawn Liu @ Dassault Systemes SolidWorks Corporation\r\n// (c) Massachusetts Institute of Technology 2016\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n//\r\n// name\r\n//\r\nvar name = 'ExtractFaces connect'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.address = getParameterByName('swIP') || '127.0.0.1'\r\n   mod.port = getParameterByName('swPort') || '80'\r\n   mod.socket = 0\r\n   mod.thickness.value = '0.75'\n   socket_open()\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   SVGArray:{type:'object',\r\n      event:function(data){\r\n          mods.output(mod, 'SVGArray', JSON.parse(data))\r\n      }\r\n   }\r\n}\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n   div.appendChild(document.createTextNode('server:'))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('address: ' + getParameterByName('swIP')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0\\u00a0\\u00a0\\u00a0port: ' + getParameterByName('swPort')))\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('\\u00a0\\u00a0status: '))\r\n   input = document.createElement('input')\r\n   input.type = 'text'\r\n   input.size = 12\r\n   div.appendChild(input)\r\n   mod.status = input\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('open'))\r\n   btn.addEventListener('click', function () {\r\n       socket_open()\r\n   })\r\n   div.appendChild(btn)\r\n   var btn = document.createElement('button')\r\n   btn.style.margin = 1\r\n   btn.appendChild(document.createTextNode('close'))\r\n   btn.addEventListener('click', function () {\r\n       socket_close()\r\n   })\r\n   div.appendChild(btn)\r\n   div.appendChild(document.createElement('br'))\r\n   div.appendChild(document.createTextNode('thickness: '))\r\n   input = document.createElement('input')\r\n      input.type = 'text'\r\n      input.size = 10\r\n      div.appendChild(input)\r\n      mod.thickness = input\r\n   div.appendChild(document.createTextNode('(inch)'))\r\n   div.appendChild(document.createElement('br'))\r\n   var btn = document.createElement('button')\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('Extract SVGs'))\r\n      btn.addEventListener('click',function() {\r\n         extract_SVGs()\r\n         })\r\n      div.appendChild(btn)\r\n   }\r\n//\r\n// local functions\r\n//\r\n\r\nfunction getParameterByName(name, url) {\r\n    if (!url) url = window.location.href;\r\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\r\n    var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\r\n        results = regex.exec(url);\r\n    if (!results) return null;\r\n    if (!results[2]) return '';\r\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\r\n}\r\n\r\nfunction socket_open() {\r\n   var url = \"ws://\"+mod.address+':'+mod.port\r\n   mod.socket = new WebSocket(url)\r\n   mod.socket.onopen = function(event) {\r\n       mod.status.value = \"opened\"\r\n       var connect = {}\r\n       connect.modCmd = 'connect'\r\n       connect.owner = getParameterByName('swOwner')\r\n       connect.id = getParameterByName('swID')\r\n       socket_send(JSON.stringify(connect))\r\n      }\r\n   mod.socket.onerror = function(event) {\r\n      mod.status.value = \"can not open\"\r\n      }\r\n   mod.socket.onmessage = function(event) {\r\n      mod.status.value = \"receive\"\r\n      var swData = JSON.parse(event.data);\r\n      if (swData.swType === \"FaceSVGArray\") {\r\n          outputs.SVGArray.event(JSON.stringify(swData.data))\r\n      }\r\n   }\r\n   mod.socket.onclose = function (event) {\r\n       mod.status.value = \"connection closed\"\r\n   }\r\n   }\r\nfunction socket_close() {\r\n   mod.socket.close()\r\n   mod.status.value = \"closed\"\r\n   mod.socket = 0\r\n   }\r\nfunction socket_send(msg) {\r\n   if (mod.socket != 0) {\r\n      mod.status.value = \"send\"\r\n      mod.socket.send(msg)\r\n      }\r\n   else {\r\n      mod.status.value = \"can't send, not open\"\r\n      }\r\n   }\r\nfunction extract_SVGs() {\r\n   var modcmd = new Object;\r\n   modcmd.modCmd = \"AutoExtractFaces\";\r\n   modcmd.thickness = Number(mod.thickness.value * 25.4); // inch to mm\r\n   socket_send(JSON.stringify(modcmd))\r\n   }\r\n\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"265.6174365523354","left":"733.2348731046708","inputs":{},"outputs":{}},"0.07126720042810941":{"definition":"//\r\n// nest multiple SVGs into a single SVG\r\n//\r\n// Sam Calisch\r\n// (c) Massachusetts Institute of Technology 2016\r\n// \r\n// This work may be reproduced, modified, distributed, performed, and \r\n// displayed for any purpose, but must acknowledge the mods\r\n// project. Copyright is retained and must be preserved. The work is \r\n// provided as is; no warranty is provided, and users accept all \r\n// liability.\r\n//\r\n// closure\r\n//\r\n(function(){\r\n//\r\n// module globals\r\n//\r\nvar mod = {}\r\n\r\nvar modParam = {\r\n  'parameters':{\r\n      'stock_width':50.,\r\n      'stock_height':24.,\r\n      'padding':.25}    //algorithm parameters, and default values\r\n}\r\n//\r\n// name\r\n//\r\nvar name = 'nest SVG Array'\r\n//\r\n// initialization\r\n//\r\nvar init = function() {\r\n   mod.stock_width.value = '50'\n   mod.stock_height.value = '24'\n   mod.padding.value = '0.25'\n\r\n   }\r\n//\r\n// inputs\r\n//\r\nvar inputs = {\r\n   SVGArray:{type:'object',\r\n      event:function(evt) {\r\n         mod.svg_array = evt.detail;\r\n         nest(mod.svg_array);\r\n         }}}\r\n//\r\n// outputs\r\n//\r\nvar outputs = {\r\n   SVG:{type:'string',\r\n      event:function(){\r\n         var str = new XMLSerializer().serializeToString(mod.bigview);\r\n         mods.output(mod,'SVG',str)}},\r\n   // TODO: make another output for parts that don't fit\r\n   //Leftovers:{type:'object',\r\n   //   event:function(){\r\n   //      mods.output(mod,'Leftovers',mod.leftovers)}}\r\n   }\r\n//\r\n// interface\r\n//\r\nvar interface = function(div){\r\n   mod.div = div\r\n\r\n   // on-screen drawing canvas\r\n   var smallview = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");//document.createElement('canvas');\r\n      smallview.setAttribute('width',mods.ui.canvas);\r\n      smallview.setAttribute('height',mods.ui.canvas);\r\n      smallview.setAttribute('preserveAspectRatio','xMinYMin meet');\r\n      div.appendChild(smallview);\r\n      mod.smallview = smallview;\r\n   div.appendChild(document.createElement('br'));\r\n\r\n   // off-screen image canvas\r\n   mod.bigview = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\"); \r\n\r\n   //add parameter inputs\r\n   Object.keys(modParam.parameters).forEach(function (p) {\r\n    var textnode = document.createElement('span');\r\n    textnode.innerHTML = p+': ';\r\n    textnode.style=\"display: inline-block; width: 80px; font-size: 12px;\";\r\n    div.appendChild(textnode);\r\n    var input_text = document.createElement('input');\r\n    var input_max = document.createElement('input');\r\n    var input_range = document.createElement('input') //add slider\r\n\r\n      //value text\r\n      input_text.type='text';\r\n      input_text.size=3;\r\n      mod[p] = input_text; //set initial minimum slider value\r\n      div.appendChild(input_text);\r\n      input_text.addEventListener('blur',function(){\r\n         input_range.value = (100 * input_text.value / input_max.value);\r\n         nest(mod.svg_array);\r\n      });\r\n\r\n      //slider\r\n      input_range.type = 'range'; \r\n      input_range.min = 0; input_range.max = 100;\r\n      input_range.value = 50; \r\n      input_range.style = '-webkit-appearance: none; width: 80px; height: 0px; border: none; margin-top: -4px; margin-left:2px;';\r\n      input_range.addEventListener('input',function(){\r\n         input_text.value = input_max.value * input_range.value/100.0;\r\n         nest(mod.svg_array);\r\n      });\r\n      div.appendChild(input_range);\r\n\r\n      //max text\r\n      input_max.type='text';\r\n      input_max.size=2;\r\n      input_max.value = 2 * modParam.parameters[p]; //set initial maximum to twice default value\r\n      input_max.addEventListener('blur',function(){\r\n         input_range.value = 100 * input_text.value / input_max.value;\r\n         input_text.value = Math.min( input_text.value, input_max.value );  \r\n         nest(mod.svg_array);\r\n      });\r\n      div.appendChild(input_max);\r\n\r\n    div.appendChild(document.createElement('br'));\r\n   });\r\n\r\n   // view button\r\n   var btn = document.createElement('button')\r\n      btn.style.padding = mods.ui.padding\r\n      btn.style.margin = 1\r\n      btn.appendChild(document.createTextNode('view'))\r\n      btn.addEventListener('click',function(){\r\n         var win = window.open('')\r\n         var btn = document.createElement('button')\r\n            btn.appendChild(document.createTextNode('close'))\r\n            btn.style.padding = mods.ui.padding\r\n            btn.style.margin = 1\r\n            btn.addEventListener('click',function(){\r\n               win.close()\r\n               })\r\n            win.document.body.appendChild(btn)\r\n         win.document.body.appendChild(document.createElement('br'))\r\n            mod.bigview.setAttribute('width', mod.stock_width.value+\"in\");\r\n            mod.bigview.setAttribute('height', mod.stock_height.value+\"in\" );\r\n            mod.bigview.setAttribute('preserveAspectRatio','xMinYMin meet');\r\n            win.document.body.appendChild(mod.bigview);\r\n         })\r\n      div.appendChild(btn)\r\n      div.appendChild(document.createTextNode(' draw grid?'));\r\n      var draw_grid = document.createElement('input')\r\n      mod.draw_grid = draw_grid;\r\n      draw_grid.type = 'checkbox'\r\n      draw_grid.name = mod.div.id+'grid'\r\n      draw_grid.id = mod.div.id+'grid'\r\n      draw_grid.checked = false\r\n      draw_grid.addEventListener('change',function(){nest(mod.svg_array);});\r\n      div.appendChild(draw_grid)\r\n   div.appendChild(document.createElement('br'))\r\n   }\r\n//\r\n// local functions\r\n\r\nfunction nest(sw_json){\r\n   if (!sw_json) return\r\n   //sw_json is text json exported from soliworks of the form [{partTitle:”Part-1”, thickness:20, count:2, svgArray:[svgf1, svgf2, …],{partTitle:”Part-2”, thickness:20, count:3, svgArray:[svgf3, svgf4, …]\r\n   //stocksize is an array [width,height] of stock dimensions\r\n   //we scale so the stock size takes up the %75 of screen\r\n   //padding is an amount to leave between each piece\r\n   var unitScale = sw_json[0].unit === \"mm\"?1000:1;\r\n   var stocksize = [mod.stock_width.value/39.3*unitScale, mod.stock_height.value/39.3*unitScale]; //convert to meters\r\n   //TODO: handle units more gracefully!\r\n   var padding = mod.padding.value/39.3*unitScale;\r\n   var draw_grid = false;\r\n\r\n   //make sure first dimension is longer\r\n   //if (stocksize[1] > stocksize[0]) stocksize = [stocksize[1],stocksize[0]];\r\n\r\n   //fit stock width to page\r\n   var scale = mod.smallview.width/stocksize[0]; \r\n\r\n   var partName = sw_json[0]['partName'];\r\n   var thickness = sw_json[0]['thickness'];\r\n   var count = sw_json[0]['count'];\r\n   var svgs = [];\r\n   //deal with multiple parts from SW\r\n   for(var i=0; i<sw_json.length; ++i){\r\n      svgs = svgs.concat(sw_json[i].svgArray);\r\n   }\r\n\r\n   //create SVG for output\r\n   var svgNS = \"http://www.w3.org/2000/svg\";\r\n   //var nested = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n   mod.smallview.innerHTML=''; //delete previous children\r\n   \r\n   //mod.smallview.setAttribute('width',(stocksize[0])*scale  );\r\n   //mod.smallview.setAttribute('height',(stocksize[1])*scale  );\r\n   mod.smallview.setAttribute('viewBox', \"0 0 \"+stocksize[0]+\" \"+stocksize[1]);\r\n\r\n   //draw stock outline\r\n   var stock = document.createElementNS(svgNS,'rect');\r\n   stock.setAttribute('x', 0); stock.setAttribute('y', 0);\r\n   stock.setAttribute('width', stocksize[0]); stock.setAttribute('height', stocksize[1]);\r\n   stock.setAttribute('style', 'fill:rgb(0,0,0);')\r\n   //stock.setAttribute('class','annotation'); //label for removal on output\r\n   mod.smallview.appendChild(stock);\r\n\r\n   var gs = [] //container for the g elements\r\n   svgs.forEach(function(svg){\r\n      var g = document.createElementNS(svgNS,'g');\r\n      g.innerHTML = svg;\r\n      var svg_tree = g.firstChild;\r\n      var vb = svg_tree.getAttribute('viewBox').split(\" \").map(parseFloat);\r\n      svg_tree.setAttribute('viewBox',(vb[0]-.5*padding)+\" \"+(vb[1]-.5*padding)+\" \"+(vb[2]+.5*padding)+\" \"+(vb[3]+.5*padding));\r\n      svg_tree.setAttribute('width', vb[2]+padding); svg_tree.setAttribute('height', vb[3]+padding);\r\n      g.setAttribute('w',vb[2]+padding); //use a foreign tag to bring width and height info along as we tranform \r\n      g.setAttribute('h',vb[3]+padding); \r\n      mod.smallview.appendChild(g);\r\n      gs.push(g);\r\n   });\r\n\r\n   //orient so long axis is horizontal. if this is not the case, rotate\r\n   gs.forEach(function(g){\r\n      w = g.getAttribute('w'); h = g.getAttribute('h');\r\n      if (h>w){\r\n         g.setAttribute('transform','translate(0,'+w+') rotate(-90)' );\r\n         g.setAttribute('w',h); g.setAttribute('h',w);\r\n      } else{\r\n         g.setAttribute('transform','' );\r\n      }\r\n   });\r\n\r\n   //then sort by long dimension in descending order\r\n   gs = gs.sort( function(g1,g2){ return g2.getAttribute('w') -  g1.getAttribute('w')} );\r\n\r\n\r\n   //then place first and calculate the left over rectangles\r\n   function fill_rect(b1,b2,remaining_shapes){\r\n      if (mod.draw_grid.checked){\r\n         node = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\r\n         node.setAttribute('x',b1[0]); node.setAttribute('y',b1[1]);\r\n         node.setAttribute('width',b2[0]-b1[0]);\r\n         node.setAttribute('height',b2[1]-b1[1]);\r\n         node.setAttribute('class','annotation'); //label for removal on output\r\n         node.setAttribute('style','fill:none;stroke-width:.001;stroke:rgb(0,0,255)');\r\n         mod.smallview.appendChild(node);\r\n      }\r\n\r\n\r\n      //fill a rectangle defined by point b1 to point b2 with the first element from remaining_shapes that fits\r\n      var dx = b2[0]-b1[0]; \r\n      var dy = b2[1]-b1[1];\r\n      for(i=0; i<remaining_shapes.length; i++){\r\n         var gi = remaining_shapes[i];\r\n         var w = parseFloat(gi.getAttribute('w'));\r\n         var h = parseFloat(gi.getAttribute('h'));\r\n         if (w <= dx+.000001 && h <= dy+.00001){ //successfully placed shape i\r\n            remaining_shapes.splice(i,1); //remove shape i from remaining shapes\r\n            gi.setAttribute('transform', 'translate('+(b1[0])+','+(b1[1])+') '+gi.getAttribute('transform')); //use base point as transform\r\n            fill_rect([b1[0],b1[1]+h],[b1[0]+w,b2[1]],remaining_shapes); //prioritize filling lower rectangle\r\n            fill_rect([b1[0]+w,b1[1]],b2,remaining_shapes);           //then fill right rectangle\r\n            break; //break out of for loop\r\n         }\r\n      }\r\n\r\n   }\r\n   fill_rect([.5*padding,.5*padding],[stocksize[0]-.5*padding,stocksize[1]-.5*padding],gs);\r\n      \r\n   //HACK: don't show parts that don't fit\r\n   gs.forEach(function(g){\r\n      g.innerHTML = ''; //delete!\r\n   });   \r\n\r\n\r\n   //create bigview svg from smallview svg\r\n   mod.bigview.setAttribute( 'viewBox', mod.smallview.getAttribute('viewBox') );\r\n   mod.bigview.innerHTML = mod.smallview.innerHTML;\r\n   mod.bigview.setAttribute('width', mod.stock_width.value+\"in\");\r\n   mod.bigview.setAttribute('height', mod.stock_height.value+\"in\" );\r\n\r\n   //output events\r\n   //mod.leftovers = [];\r\n   outputs.SVG.event()\r\n\r\n\r\n   /*\r\n\r\n   //highlight parts that didn't fit\r\n   gs.forEach(function(g){\r\n      var svg = g.firstChild;\r\n      var style = svg.firstChild.getAttribute('style');\r\n      svg.firstChild.setAttribute('style',style+'stroke-width:'+.002*stocksize[0]+'; stroke:rgb(255,0,0)')\r\n   });\r\n   //nest parts that don't fit, note: this could just be done on another page.\r\n   if (gs.length > 0){\r\n      fill_rect([.5*padding,.5*padding],[stocksize[0]-.5*padding,stocksize[1]-.5*padding],gs);\r\n   }\r\n   */\r\n}\r\n\r\n//\r\n// return values\r\n//\r\nreturn ({\r\n   mod:mod,\r\n   name:name,\r\n   init:init,\r\n   inputs:inputs,\r\n   outputs:outputs,\r\n   interface:interface\r\n   })\r\n}())\r\n","top":"864.8310762515605","left":"906.7919450294961","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.23780413326993044\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5857417886002868\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5857417886002868\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6248369051648597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.49036025089153756\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.23780413326993044\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.49036025089153756\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.32734870523599846\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"diameter\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.49036025089153756\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"diameter\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6248369051648597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8617147326718335\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8617147326718335\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.14150626327820492\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVGArray\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07126720042810941\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVGArray\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.07126720042810941\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5857417886002868\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}"]}
\ No newline at end of file