From 68bfa0907622c4779d89154a010977713e0c0883 Mon Sep 17 00:00:00 2001
From: yuichitamiya <y2miyacatsfab@gmail.com>
Date: Thu, 17 Feb 2022 23:09:25 +0900
Subject: [PATCH] swap mill_2D_PCB_SVG

---
 .../images/proverXL/mods/mill_2D_PCB_svg.html | 3029 ++++++-----------
 1 file changed, 1124 insertions(+), 1905 deletions(-)

diff --git a/docs/Instruction/images/proverXL/mods/mill_2D_PCB_svg.html b/docs/Instruction/images/proverXL/mods/mill_2D_PCB_svg.html
index d873f5a..b3b8525 100644
--- a/docs/Instruction/images/proverXL/mods/mill_2D_PCB_svg.html
+++ b/docs/Instruction/images/proverXL/mods/mill_2D_PCB_svg.html
@@ -4,1911 +4,1130 @@
 </head>
 <body link='black' alink='black' vlink='black'>
 <script>
-//
-// mods.js
-//
-// Neil Gershenfeld
-// (c) Massachusetts Institute of Technology 2018
-//
-// 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(){
-//
-// globals
-//
-var mods = {}
-mods.mod = {}
-mods.globals = {}
-mods.ui = {source:null,
-   progname:'',
-   padding:7,
-   bezier:100,
-   canvas:250,
-   rows:5,
-   cols:20,
-   link_color:'rgb(0,0,128)',
-   link_highlight:'rgb(255,0,0)',
-   header:50,
-   selected:{},
-   mousedown:null,
-   menu:null,
-   top:null,
-   left:null,
-   xstart:null,
-   ystart:null,
-   xtrans:null,
-   ytrans:null
-   }
-//
-// UI
-//
-document.body.style.overflow = "hidden"
-function mods_transform() {
-   var transform = document.body.style.transform
-   var m = new DOMMatrix(getComputedStyle(document.body).transform)
-   var s = m.m11
-   var tx = m.m41/s
-   var ty = m.m42/s
-   var origin = document.body.style.transformOrigin
-      var pxx = origin.indexOf('px')
-      var ox = parseFloat(origin.slice(0,pxx))
-      var pxy = origin.indexOf('px',pxx+2)
-      var oy = parseFloat(origin.slice(pxx+2,pxy))
-   return({s:s,tx:tx,ty:ty,ox:ox,oy:oy})
-   }
-document.body.style.transform = 'scale(1) translate(0px,0px)'
-document.body.style.transformOrigin = '0px 0px'
-//
-// scroll wheel event
-//
-window.addEventListener('wheel',function(evt) {
-   /*
-   (xw+tx-ox)*s+ox = xs
-   xw = ox-tx+(xs-ox)/s
-   (xw+tx0-ox0)*s+ox0  = (xw+tx1-ox1)*s+ox1
-   (tx0-ox0)*s+ox0  = (tx1-ox1)*s+ox1
-   tx0+(ox1-ox0)+(ox0-ox1)/s  = tx1
-   tx0+(ox1-ox0)*(1-1/s)  = tx1
-   */
-   var el = document.elementFromPoint(evt.pageX,evt.pageY)
-   if ((el.tagName == "HTML") || (el.tagName == "BODY")) {
-      set_prompt('scroll to zoom')
-      evt.preventDefault()
-      evt.stopPropagation()
-      var t = mods_transform()
-      if (evt.deltaY > 0)
-         var scale = t.s*1.1
-      else
-         var scale = t.s*0.9
-      var tx = t.tx+(evt.pageX-t.ox)*(1-1/t.s)
-      var ty = t.ty+(evt.pageY-t.oy)*(1-1/t.s)
-      document.body.style.transform = `scale(${scale}) translate(${tx}px,${ty}px)`
-      document.body.style.transformOrigin = `${evt.pageX}px ${evt.pageY}px`
-      }
-   })
-//
-// body mouse events
-//
-window.addEventListener('mousedown',function(evt) {
-   //
-   // get element mouse is over
-   //
-   var el = document.elementFromPoint(evt.pageX,evt.pageY)
-   //
-   // check if on body
-   //
-   if ((el.tagName == "HTML") || (el.tagName == "BODY")) {
-      //
-      // remember button
-      //
-      mods.ui.mousedown = evt.button
-      if (mods.ui.mousedown == 0) {
-         set_prompt('left-drag to pan, right-drag to select')
-         if (mods.ui.menu != null) {
-            document.body.removeChild(mods.ui.menu)
-            mods.ui.menu = null
-            }
-         }
-      else if (mods.ui.mousedown == 2) {
-         set_prompt('menu; left-drag to pan, right-drag to select')
-         }
-      //
-      // remember position
-      //
-      var t = mods_transform()
-      mods.ui.xstart = evt.pageX
-      mods.ui.ystart = evt.pageY
-      mods.ui.xtrans = t.tx
-      mods.ui.ytrans = t.ty
-      }
-   })
-window.addEventListener('mousemove',function(evt) {
-   //
-   // mouse move
-   //
-   if (mods.ui.mousedown != null) {
-      evt.preventDefault()
-      evt.stopPropagation()
-      var t = mods_transform()
-      if (mods.ui.mousedown == 0) {
-         //
-         // pan on left drag
-         //
-         xtrans = mods.ui.xtrans+(evt.pageX-mods.ui.xstart)/t.s
-         ytrans = mods.ui.ytrans+(evt.pageY-mods.ui.ystart)/t.s
-         document.body.style.transform = `scale(${t.s}) translate(${xtrans}px,${ytrans}px)`
-         }
-      else if (mods.ui.mousedown == 2) {
-         //
-         // select on right drag
-         //
-         var rect = document.getElementById('svgrect')
-         if (rect == undefined) {
-            //
-            // start dragging
-            //
-            if (mods.ui.menu != null) {
-               document.body.removeChild(mods.ui.menu)
-               mods.ui.menu = null
-               }
-            set_prompt('right-drag to select')
-            var t = mods_transform()
-            var rect = document.createElementNS('http://www.w3.org/2000/svg','rect')
-               rect.setAttribute('id','svgrect')
-               rect.setAttribute('x',t.ox-t.tx+(evt.pageX-t.ox)/t.s)
-               rect.setAttribute('y',t.oy-t.ty+(evt.pageY-t.oy)/t.s-mods.ui.header)
-               rect.setAttribute('width',0)
-               rect.setAttribute('height',0)
-               rect.setAttribute('fill','rgb(200,200,200)')
-               rect.setAttribute('stroke','none')
-            var svg = document.getElementById('svg')
-               svg.insertBefore(rect,svg.firstChild)
-            }
-         else {
-            //
-            // continue dragging
-            //
-            var rect = document.getElementById('svgrect')
-            var xp = t.ox-t.tx+(mods.ui.xstart-t.ox)/t.s
-            var yp = t.oy-t.ty+(mods.ui.ystart-t.oy)/t.s-mods.ui.header
-            var xw = t.ox-t.tx+(evt.pageX-t.ox)/t.s
-            var yw = t.oy-t.ty+(evt.pageY-t.oy)/t.s-mods.ui.header
-            if (xw < xp) {
-               rect.setAttribute('x',xw)
-               rect.setAttribute('width',xp-xw)
-               }
-            else
-               rect.setAttribute('width',xw-xp)
-            if (yw < yp) {
-               rect.setAttribute('y',yw)
-               rect.setAttribute('height',yp-yw)
-               }
-            else
-               rect.setAttribute('height',yw-yp)
-            }
-         }
-      }
-   })
-window.addEventListener('mouseup',function(evt) {
-   //
-   // mouse up
-   //
-   mods.ui.mousedown = null
-   //
-   // check for selection rectangle
-   //
-   var rect = document.getElementById('svgrect')
-   if (rect != null) {
-      //
-      // rectangle exists, selecting modules
-      //
-      var x = parseFloat(rect.getAttribute('x'))
-      var y = parseFloat(rect.getAttribute('y'))
-      var width = parseFloat(rect.getAttribute('width'))
-      var height = parseFloat(rect.getAttribute('height'))
-      svg.removeChild(rect)
-      var modules = document.getElementById('modules')
-      //
-      // loop to find selected modules
-      //
-      mods.ui.selected = {}
-      for (var module in modules.childNodes) {
-         var container = modules.childNodes[module]
-         var id = container.id
-         if (id != undefined) {
-            var name = container.firstChild
-            var left = parseFloat(container.dataset.left)
-            var top = parseFloat(container.dataset.top)
-            if ((x <= left) && (left <= x+width) && (y <= top) && (top <= y+height)) {
-               //
-               // module is in selection rectangle
-               //
-               name.style.fontWeight = "bold"
-               mods.ui.selected[id] = true
-               }
-            else {
-               //
-               // module is not in selection rectangle
-               //
-               name.style.fontWeight = "normal"
-               }
-            }
-         }
-      }
-   })
-//
-// context menu
-//
-window.addEventListener('contextmenu',function(evt){
-   evt.stopPropagation()
-   evt.preventDefault()
-   if (mods.ui.menu != null) {
-      document.body.removeChild(mods.ui.menu)
-      mods.ui.menu = null
-      }
-   var div = document.createElement('div')
-   make_menu(div)
-   add_menu(div,'modules',modules)
-   add_menu(div,'programs',programs)
-   add_menu(div,'edit',edit)
-   add_menu(div,'options',options)
-   document.body.appendChild(div)
-   function make_menu(div) {
-      mods.ui.menu = div
-      div.style.position = "absolute"
-      var t = mods_transform()
-      div.style.top = t.oy-t.ty+(evt.pageY-t.oy)/t.s
-      div.style.left = t.ox-t.tx+(evt.pageX-t.ox)/t.s
-      div.style.zIndex = 0
-      div.style.cursor = 'default'
-      div.style.backgroundColor = "rgb(220,255,255)"
-      div.style.padding = 1.5*mods.ui.padding
-      div.style.textAlign = 'left'
-      div.style.border = '2px solid'
-      div.style.borderRadius = '10px'
-      }
-   function add_menu(div,text,click) {
-      var textdiv = document.createElement('div')
-      textdiv.appendChild(document.createTextNode(text))
-      textdiv.appendChild(document.createElement('br'))
-      textdiv.addEventListener('mouseover',function(evt){
-         evt.target.style.fontWeight = 'bold'})
-      textdiv.addEventListener('mouseout',function(evt){
-         evt.target.style.fontWeight = 'normal'})
-      textdiv.addEventListener('mousedown',click)
-      textdiv.addEventListener('touchend',click)
-      div.appendChild(textdiv)
-      }
-   //
-   // modules menu
-   //
-   function modules(evt) {
-      evt.preventDefault()
-      evt.stopPropagation()
-      document.body.removeChild(evt.target.parentNode)
-      var div = document.createElement('div')
-      make_menu(div)
-      set_prompt('modules')
-      //
-      // open server module
-      //
-      add_menu(div,'open server module',function(evt){
-         function module_label(label) {
-            var div = document.createElement('div')
-            var i = document.createElement('i')
-            i.appendChild(document.createTextNode(label))
-            div.appendChild(i)
-            div.appendChild(document.createElement('br'))
-            menu.appendChild(div)
-            }
-         function module_menu(label,module) {
-            var div = document.createElement('div')
-            div.appendChild(
-               document.createTextNode('\u00A0\u00A0\u00A0'+label))
-            div.addEventListener('mouseover',function(evt){
-               evt.target.style.fontWeight = 'bold'})
-            div.addEventListener('mouseout',function(evt){
-               evt.target.style.fontWeight = 'normal'})
-            div.addEventListener('mousedown',function(evt){
-               evt.preventDefault()
-               evt.stopPropagation()
-               document.body.removeChild(evt.target.parentNode)
-               mods.ui.menu = null
-               var t = mods_transform()
-               mod_message_handler(module,
-                  t.oy-t.ty+(evt.pageY-t.oy)/t.s,
-                  t.ox-t.tx+(evt.pageX-t.ox)/t.s)})
-            div.appendChild(document.createElement('br'))
-            menu.appendChild(div)
-            }
-         document.body.removeChild(evt.target.parentNode)
-         var menu = document.createElement('div')
-         make_menu(menu)
-         document.body.appendChild(menu)
-         menu.style.width = mods.ui.canvas
-         menu.style.height = mods.ui.canvas
-         menu.style.overflow = 'auto'
-         var req = new XMLHttpRequest()
-         req.responseType = 'text'
-         req.onreadystatechange = function() {
-            if (req.readyState == XMLHttpRequest.DONE) {
-               var str = req.response
-               eval(str)
-               }
-            }
-         req.open('GET','modules/index.js'+'?rnd='+Math.random())
-         req.send()
-         })
-      //
-      // open local module
-      //
-      add_menu(div,'open local module',function(evt){
-         var t = mods_transform()
-         mods.ui.top = t.oy-t.ty+(evt.pageY-t.oy)/t.s
-         mods.ui.left = t.ox-t.tx+(evt.pageX-t.ox)/t.s
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         var file = document.getElementById('mod_input')
-         file.value = null
-         file.click()
-         })
-      //
-      // open remote module
-      //
-      add_menu(div,'open remote module',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         set_prompt('remotes not yet implemented')
-         })
-      document.body.appendChild(div)
-      }
-   //
-   // programs menu
-   //
-   function programs(evt) {
-      evt.preventDefault()
-      evt.stopPropagation()
-      document.body.removeChild(evt.target.parentNode)
-      var div = document.createElement('div')
-      make_menu(div)
-      set_prompt('programs')
-      //
-      // open local program
-      //
-      add_menu(div,'open local program',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         var file = document.getElementById('prog_input')
-         file.value = null
-         file.click()
-         })
-      //
-      // open server program
-      //
-      add_menu(div,'open server program',function(evt){
-         function program_label(label) {
-            var div = document.createElement('div')
-            var i = document.createElement('i')
-            i.appendChild(document.createTextNode(label))
-            div.appendChild(i)
-            div.appendChild(document.createElement('br'))
-            menu.appendChild(div)
-            }
-         function program_menu(label,program) {
-            var div = document.createElement('div')
-            div.appendChild(
-               document.createTextNode('\u00A0\u00A0\u00A0'+label))
-            div.addEventListener('mouseover',function(evt){
-               evt.target.style.fontWeight = 'bold'})
-            div.addEventListener('mouseout',function(evt){
-               evt.target.style.fontWeight = 'normal'})
-            div.addEventListener('mousedown',function(evt){
-               evt.preventDefault()
-               evt.stopPropagation()
-               if (location.port == 80)
-                  var uri = 'http://'+location.hostname
-                     +'?program='+program
-               else
-                  var uri = 'http://'+location.hostname+':'
-                     +location.port+'?program='+program
-               set_prompt('<a href='+uri+'>program link</a>')
-               prog_message_handler(program)
-               document.body.removeChild(evt.target.parentNode)
-               mods.ui.menu = null
-               })
-            div.appendChild(document.createElement('br'))
-            menu.appendChild(div)
-            }
-         document.body.removeChild(evt.target.parentNode)
-         var menu = document.createElement('div')
-         make_menu(menu)
-         document.body.appendChild(menu)
-         menu.style.width = mods.ui.canvas
-         menu.style.height = mods.ui.canvas
-         menu.style.overflow = 'auto'
-         var req = new XMLHttpRequest()
-         req.responseType = 'text'
-         req.onreadystatechange = function() {
-            if (req.readyState == XMLHttpRequest.DONE) {
-               var str = req.response
-               eval(str)
-               }
-            }
-         req.open('GET','programs/index.js'+'?rnd='+Math.random())
-         req.send()
-         })
-      //
-      // open remote program
-      //
-      add_menu(div,'open remote program',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         set_prompt('remotes not yet implemented')
-         })
-      //
-      // save local program
-      //
-      add_menu(div,'save local program',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         save_program()
-         })
-      //
-      // save local page
-      //
-      add_menu(div,'save local page',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         save_page()
-         })
-      document.body.appendChild(div)
-      }
-   //
-   // edit menu
-   //
-   function edit(evt) {
-      evt.preventDefault()
-      evt.stopPropagation()
-      document.body.removeChild(evt.target.parentNode)
-      var div = document.createElement('div')
-      make_menu(div)
-      set_prompt('edit')
-      //
-      // cut
-      //
-      add_menu(div,'cut',function(evt){
-         evt.preventDefault()
-         evt.stopPropagation()
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         if ((Object.keys(mods.ui.selected).length) == 0) {
-            set_prompt("nothing selected")
-            }
-         else {
-            for (var id in mods.ui.selected) {
-               var div = document.getElementById(id)
-               delete_module(id)
-               mods.ui.selected = {}
-               }
-            }
-         })
-      //
-      // copy
-      //
-      add_menu(div,'copy',function(evt){
-         evt.preventDefault()
-         evt.stopPropagation()
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         set_prompt('copy not yet implemented')
-         })
-      //
-      // paste
-      //
-      add_menu(div,'paste',function(evt){
-         evt.preventDefault()
-         evt.stopPropagation()
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         set_prompt('paste not yet implemented')
-         })
-      document.body.appendChild(div)
-      }
-   //
-   // options menu
-   //
-   function options(evt) {
-      evt.preventDefault()
-      evt.stopPropagation()
-      document.body.removeChild(evt.target.parentNode)
-      mods.ui.menu = null
-      var div = document.createElement('div')
-      make_menu(div)
-      set_prompt('options')
-      //
-      // view files
-      //
-      add_menu(div,'view files',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         var win = window.open('files.html')
-         })
-      document.body.appendChild(div)
-      //
-      // view project
-      //
-      add_menu(div,'view project',function(evt){
-         document.body.removeChild(evt.target.parentNode)
-         mods.ui.menu = null
-         var win = window.open('https://gitlab.cba.mit.edu/pub/mods')
-         })
-      document.body.appendChild(div)
-      }
-   })
-//
-// prompt
-//
-document.body.appendChild(document.createTextNode(' '))
-var span = document.createElement('span')
-   span.setAttribute('id','logo')
-   span.style.display = 'inline-block'
-   span.style.verticalAlign = 'middle'
-   span.style.width = 20
-   span.style.height = 20
-   span.style.padding = mods.ui.padding
-   span.appendChild(logo(1))
-   document.body.appendChild(span)
-document.body.appendChild(document.createTextNode(' '))
-var span = document.createElement('span')
-   span.setAttribute('id','prompt')
-   span.style.display = 'inline-block'
-   span.style.verticalAlign = 'middle'
-   var innerspan = document.createElement('span')
-      span.appendChild(innerspan)
-   document.body.appendChild(span)
-function logo(size) {
-   var x = 0
-   var y = 2.8*size/3.8
-   var svgNS = "http://www.w3.org/2000/svg"
-   var logo = document.createElementNS(svgNS,"svg")
-   logo.setAttributeNS("http://www.w3.org/2000/xmlns/",
-      "xmlns:xlink","http://www.w3.org/1999/xlink")
-   logo.setAttributeNS(null,'viewBox',"0 0 "+size+" "+size)
-   var new_rect = document.createElementNS(svgNS,"rect");
-   new_rect.setAttribute("width",size/3.8)
-   new_rect.setAttribute("height",size/3.8)
-   new_rect.setAttribute("x",x)
-   new_rect.setAttribute("y",y)
-   new_rect.setAttribute("fill","blue")
-   logo.appendChild(new_rect)
-   var new_rect = document.createElementNS(svgNS,"rect");
-   new_rect.setAttribute("width",size/3.8)
-   new_rect.setAttribute("height",size/3.8)
-   new_rect.setAttribute("x",x+1.4*size/3.8)
-   new_rect.setAttribute("y",y)
-   new_rect.setAttribute("fill","blue")
-   logo.appendChild(new_rect)
-   var new_rect = document.createElementNS(svgNS,"rect");
-   new_rect.setAttribute("width",size/3.8)
-   new_rect.setAttribute("height",size/3.8)
-   new_rect.setAttribute("x",x+2.8*size/3.8)
-   new_rect.setAttribute("y",y)
-   new_rect.setAttribute("fill","blue")
-   logo.appendChild(new_rect)
-   var new_rect = document.createElementNS(svgNS, "rect");
-   new_rect.setAttribute("width",size/3.8)
-   new_rect.setAttribute("height",size/3.8)
-   new_rect.setAttribute("x",x)
-   new_rect.setAttribute("y",y-1.4*size/3.8)
-   new_rect.setAttribute("fill","blue")
-   logo.appendChild(new_rect)
-   var new_rect = document.createElementNS(svgNS, "rect");
-   new_rect.setAttribute("width", size / 3.8)
-   new_rect.setAttribute("height", size / 3.8)
-   new_rect.setAttribute("x", x + 2.8 * size / 3.8)
-   new_rect.setAttribute("y", y - 1.4 * size / 3.8)
-   new_rect.setAttribute("fill", "blue")
-   logo.appendChild(new_rect)
-   var new_rect = document.createElementNS(svgNS, "rect");
-   new_rect.setAttribute("width", size / 3.8)
-   new_rect.setAttribute("height", size / 3.8)
-   new_rect.setAttribute("x", x + 1.4 * size / 3.8)
-   new_rect.setAttribute("y", y - 2.8 * size / 3.8)
-   new_rect.setAttribute("fill", "blue")
-   logo.appendChild(new_rect)
-   var new_rect = document.createElementNS(svgNS, "rect");
-   new_rect.setAttribute("width", size / 3.8)
-   new_rect.setAttribute("height", size / 3.8)
-   new_rect.setAttribute("x", x + 2.8 * size / 3.8)
-   new_rect.setAttribute("y", y - 2.8 * size / 3.8)
-   new_rect.setAttribute("fill", "blue")
-   logo.appendChild(new_rect)
-   var new_circ = document.createElementNS(svgNS, "circle");
-   new_circ.setAttribute("r", size / (2 * 3.8))
-   new_circ.setAttribute("cx", x + size / (2 * 3.8))
-   new_circ.setAttribute("cy", y + size / (2 * 3.8) - 2.8 * size / 3.8)
-   new_circ.setAttribute("fill", "red")
-   logo.appendChild(new_circ)
-   var new_circ = document.createElementNS(svgNS, "circle");
-   new_circ.setAttribute("r", size / (2 * 3.8))
-   new_circ.setAttribute("cx", x + size / (2 * 3.8) + 1.4 * size / 3.8)
-   new_circ.setAttribute("cy", y + size / (2 * 3.8) - 1.4 * size / 3.8)
-   new_circ.setAttribute("fill", "red")
-   logo.appendChild(new_circ)
-   return logo
-   }
-set_prompt('right click/two finger/long press for menu; scroll for zoom, drag for pan')
-//
-// SVG canvas for drawing
-//
-var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
-   svg.style.position = 'absolute'
-   svg.style.backgroundColor = 'rgb(255,255,255)'
-   svg.style.top = mods.ui.header
-   svg.style.left = 0
-   svg.style.zIndex = 0
-   svg.style.overflow = 'visible'
-   svg.setAttribute('width',40)
-   svg.setAttribute('height',40)
-   svg.setAttribute('id','svg')
-   svg.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink")
-   document.body.appendChild(svg)
-//
-// link container
-//
-var svg = document.getElementById('svg')
-var g = document.createElementNS('http://www.w3.org/2000/svg','g')
-   g.setAttribute('id','links')
-   svg.appendChild(g)
-//
-// file reading controls
-//
-var file = document.createElement('input')
-   file.setAttribute('type','file')
-   file.setAttribute('id','mod_input')
-   file.style.position = 'absolute'
-   file.style.left = 0
-   file.style.top = 0
-   file.style.width = 0
-   file.style.height = 0
-   file.style.opacity = 0
-   file.addEventListener('change',function() {
-      mod_read_handler()
-      })
-   document.body.appendChild(file)
-var file = document.createElement('input')
-   file.setAttribute('type','file')
-   file.setAttribute('id','prog_input')
-   file.style.position = 'absolute'
-   file.style.left = 0
-   file.style.top = 0
-   file.style.width = 0
-   file.style.height = 0
-   file.style.opacity = 0
-   file.addEventListener('change',function() {
-      prog_read_handler()
-      })
-   document.body.appendChild(file)
-//
-// module container
-//
-var div = document.createElement('div')
-   div.setAttribute('id','modules')
-   document.body.appendChild(div)
-//
-// check for program load query
-//
-if (location.search.length > 0) {
-   var args = location.search.slice(1).split('&')
-   for (var a in args) {
-      var arg = args[a].split('=')
-      if (arg[0] == 'program')
-      prog_message_handler(arg[1])
-      }
-   }
-//
-// program routines
-//
-function prog_read_handler(event) {
-   var file = document.getElementById('prog_input')
-   var file_reader = new FileReader()
-   file_reader.onload = prog_load_handler
-   file_reader.readAsText(file.files[0])
-   mods.ui.progname = file.files[0].name
-   }
-function prog_message_handler(filename) {
-   var req = new XMLHttpRequest()
-   req.responseType = 'text'
-   req.onreadystatechange = function() {
-      if (req.readyState == XMLHttpRequest.DONE) {
-         prog = JSON.parse(req.response)
-         prog_load(prog)
-         }
-      }
-   var index = filename.lastIndexOf('/')
-   if (index != -1) {
-      mods.ui.progname = filename.slice(index+1)
-      }
-   else
-      mods.ui.progname = filename
-   //
-   // send request, with random query to prevent caching
-   //
-   req.open('GET',filename+'?rnd='+Math.random())
-   req.send()
-   }
-function prog_load_handler(event) {
-   prog = JSON.parse(event.target.result)
-   prog_load(prog)
-   }
-function prog_load(prog) {
-   //
-   // load modules
-   //
-   for (var idnumber in prog.modules) {
-      var module = prog.modules[idnumber]
-      var str = module.definition
-      try {
-         eval('var args = '+str)
-         }
-      catch (err) {
-         console.log(err.message)
-         return
-         }
-      args.definition = str
-      args.id = idnumber
-      var t = mods_transform()
-      var xw = t.ox-t.tx+(mods.ui.xstart-t.ox)/t.s
-      var yw = t.oy-t.ty+(mods.ui.ystart-t.oy)/t.s-mods.ui.header
-      args.top = parseFloat(module.top)+yw
-      args.left = parseFloat(module.left)+xw
-      args.filename = module.filename
-      add_module(args)
-      }
-   //
-   // load links
-   //
-   for (var linkid in prog.links) {
-      var str = prog.links[linkid]
-      eval('var link = '+str)
-      eval('var linksrc = '+link.source)
-      eval('var linkdst = '+link.dest)
-      var src = document.getElementById(
-         JSON.stringify({id:linksrc.id,type:linksrc.type,name:linksrc.name}))
-      var dst = document.getElementById(
-         JSON.stringify({id:linkdst.id,type:linkdst.type,name:linkdst.name}))
-      add_link(src,dst)
-      }
-   }
-function save_program() {
-   set_prompt('program name? ')
-   get_prompt(mods.ui.progname,function(filename){
-      mods.ui.progname = filename
-      var prog = {modules:{},links:[]}
-      var modules = document.getElementById('modules')
-      //
-      // save modules
-      //
-      for (var c = 0; c < modules.childNodes.length; ++c) {
-         var module = modules.childNodes[c]
-         var idnumber = module.id
-         prog.modules[idnumber] = {
-            definition:update_module_definition(
-               idnumber,module.dataset.definition),
-            top:module.dataset.top,
-            left:module.dataset.left,
-            filename: module.dataset.filename,
-            inputs:{},
-            outputs:{}
-            }
-         }
-      //
-      // save links
-      //
-      var svg = document.getElementById('svg')
-      var links = svg.getElementById('links')
-         for (var l = 0; l < links.childNodes.length; ++l) {
-            var link = links.childNodes[l]
-            var linkid = link.id
-            prog.links.push(linkid)
-            }
-      //
-      // download
-      //
-      var text = JSON.stringify(prog)
-      var a = document.createElement('a')
-      a.setAttribute('href','data:text/plain;charset=utf-8,'+
-         encodeURIComponent(text))
-      a.setAttribute('download',filename)
-      a.style.display = 'none'
-      document.body.appendChild(a)
-      a.click()
-      document.body.removeChild(a)
-      })
-   }
-function save_page() {
-   set_prompt('page name? ')
-   get_prompt(mods.ui.progname+".html",function(filename){
-      mods.ui.progname = filename
-      var prog = {modules:{},links:[]}
-      var modules = document.getElementById('modules')
-      //
-      // save modules
-      //
-      for (var c = 0; c < modules.childNodes.length; ++c) {
-         var module = modules.childNodes[c]
-         var idnumber = module.id
-         prog.modules[idnumber] = {
-            definition:update_module_definition(
-               idnumber,module.dataset.definition),
-            top:module.dataset.top,
-            left:module.dataset.left,
-	    filename: module.dataset.filename,
-            inputs:{},
-            outputs:{}
-            }
-         }
-      //
-      // save links
-      //
-      var svg = document.getElementById('svg')
-      var links = svg.getElementById('links')
-         for (var l = 0; l < links.childNodes.length; ++l) {
-            var link = links.childNodes[l]
-            var linkid = link.id
-            prog.links.push(linkid)
-            }
-      //
-      // read mods.js
-      //
-      var req = new XMLHttpRequest()
-      req.responseType = 'text'
-      req.onreadystatechange = function() {
-         if (req.readyState == XMLHttpRequest.DONE) {
-            //
-            // construct page
-            //
-            var str = req.response
-            var text ="<html>\n"
-            text += "<head><meta charset='utf-8'>\n"
-            text += "<title>mods</title>\n"
-            text += "</head>\n"
-            text += "<body link='black' alink='black' vlink='black'>\n"
-            text += "<"+"script"+">\n"
-            text += str
-            text += "var prog = JSON.parse(JSON.stringify("+JSON.stringify(prog)+"))\n"
-            text += "window.mods_prog_load(prog)\n"
-            text += "</"+"script"+">\n"
-            text += "</body>\n"
-            text += "</html>\n"
-            //
-            // download page
-            //
-            var a = document.createElement('a')
-            a.setAttribute('href','data:text/plain;charset=utf-8,'+
-               encodeURIComponent(text))
-            a.setAttribute('download',filename)
-            a.style.display = 'none'
-            document.body.appendChild(a)
-            a.click()
-            document.body.removeChild(a)
-            }
-         }
-      //
-      // send request, with random query to prevent caching
-      //
-      req.open('GET','js/mods.js'+'?rnd='+Math.random())
-      req.send()
-      })
-   }
-//
-// add program load to window
-//
-window.mods_prog_load = function(prog) {
-   prog_load(prog)
-   }
-//
-// module routines
-//
-function mod_read_handler(event) {
-   var file = document.getElementById('mod_input')
-   var file_reader = new FileReader()
-   file_reader.onload = mod_load_handler
-   file_reader.readAsText(file.files[0])
-   }
-function mod_message_handler(filename,top,left) {
-   var req = new XMLHttpRequest()
-   req.responseType = 'text'
-   req.onreadystatechange = function() {
-      if (req.readyState == XMLHttpRequest.DONE) {
-         var str = req.response
-         try {
-            eval('var args = '+str)
-            }
-         catch (err) {
-            console.log(err.message)
-            return
-            }
-         args.definition = str
-         args.id = String(Math.random())
-         args.top = top
-         args.left = left
-         args.filename = filename
-         add_module(args)
-         }
-      }
-   //
-   // send request, with random query to prevent caching
-   //
-   req.open('GET',filename+'?rnd='+Math.random())
-   req.send()
-   }
-function mod_load_handler(event) {
-   str = event.target.result
-   try {
-      eval('var args = '+str)
-      }
-   catch (err) {
-      console.log(err.message)
-      return
-      }
-   args.definition = str
-   args.id = String(Math.random())
-   args.top = mods.ui.top
-   args.left = mods.ui.left
-   args.filename = ""
-   var div = add_module(args)
-   return(div)
-   }
-function add_module(args) {
-   var idnumber = args.id
-   mods.mod[idnumber] = args.mod
-   var modules = document.getElementById('modules')
-   //
-   // container
-   //
-   var container = document.createElement('div')
-      container.setAttribute("id",idnumber)
-      container.style.position = "absolute"
-      container.style.top = args.top
-      container.style.left = args.left
-      container.dataset.top = args.top
-      container.dataset.left = args.left
-      container.dataset.filename = args.filename
-      container.dataset.name = args.name
-      container.style.zIndex = 0
-      container.style.width = window.innerWidth
-      container.dataset.definition = args.definition
-      modules.appendChild(container)
-   //
-   // name
-   //
-   var divname = document.createElement('div')
-      divname.appendChild(document.createTextNode(args.name))
-      divname.addEventListener('mouseover',name_over)
-      divname.addEventListener('mouseout',name_out)
-      divname.addEventListener('mousedown',name_mousedown)
-      divname.addEventListener('touchstart',name_touchdown)
-      divname.style.backgroundColor = "rgb(210,240,210)"
-      divname.style.padding = 1.5*mods.ui.padding
-      divname.style.position = "absolute"
-      divname.style.cursor = 'default'
-      divname.style.top = 0
-      divname.style.left = 0
-      divname.style.textAlign = 'center'
-      divname.style.border = '2px solid'
-      divname.style.borderRadius = '10px'
-      container.appendChild(divname)
-   //
-   // controls
-   //
-   var divctrl = document.createElement('div')
-      var editspan = document.createElement('span')
-         editspan.innerHTML = 'edit'
-         editspan.style.fontWeight = 'normal'
-         editspan.addEventListener('mouseover',function(event){
-            set_prompt('click to edit')
-            editspan.style.fontWeight = 'bold'})
-         editspan.addEventListener('mouseout',function(event){
-            set_prompt('')
-            editspan.style.fontWeight = 'normal'})
-         editspan.addEventListener('mousedown',edit_module)
-         divctrl.appendChild(editspan)
-      var delspan = document.createElement('span')
-         delspan.innerHTML = ' delete '
-         delspan.addEventListener('mouseover',function(event){
-            set_prompt('click to delete')
-            delspan.style.fontWeight = 'bold'})
-         delspan.addEventListener('mouseout',function(event){
-            set_prompt('')
-            delspan.style.fontWeight = 'normal'})
-         delspan.addEventListener('mousedown',function(event){
-            delete_module(event.target.parentNode.parentNode.id)})
-         divctrl.appendChild(delspan)
-      divctrl.style.backgroundColor = "rgb(240,220,220)"
-      divctrl.style.padding = mods.ui.padding
-      divctrl.style.position = "absolute"
-      divctrl.style.cursor = 'default'
-      divctrl.style.top = divname.clientHeight
-      divctrl.style.left = 0
-      divctrl.style.textAlign = 'center'
-      divctrl.style.border = '2px solid'
-      divctrl.style.borderRadius = '10px'
-      container.appendChild(divctrl)
-      divctrl.style.left = divname.clientWidth/2-divctrl.clientWidth/2
-   //
-   // interface
-   //
-   var divint = document.createElement('div')
-      divint.style.backgroundColor = "rgb(240,240,240)"
-      divint.style.padding = mods.ui.padding
-      divint.style.position = "absolute"
-      divint.style.top = divname.clientHeight+divctrl.clientHeight
-      divint.style.textAlign = 'center'
-      divint.style.border = '2px solid'
-      divint.style.borderRadius = '10px'
-      divint.setAttribute('id',JSON.stringify({id:idnumber,type:'interface'}))
-      divint.dataset.id = idnumber
-      divint.dataset.divNameSize = divname.clientWidth
-      args.interface(divint)
-      container.appendChild(divint)
-      divint.style.left = divname.clientWidth/2-divint.clientWidth/2
-   //
-   // inputs
-   //
-   var divin = document.createElement('div')
-      divin.setAttribute('id',JSON.stringify({id:idnumber,type:'inputs'}))
-      var b = document.createElement('b')
-         b.appendChild(document.createTextNode('inputs'))
-         divin.appendChild(b)
-      divin.style.backgroundColor = "rgb(240,240,210)"
-      divin.style.padding = mods.ui.padding
-      divin.style.paddingLeft = '2px'
-      divin.style.position= "absolute"
-      divin.style.top = divname.clientHeight+divctrl.clientHeight
-      divin.style.textAlign = 'left'
-      divin.style.border = '2px solid'
-      divin.style.borderRadius = '10px'
-      divin.setAttribute('id',JSON.stringify({id:idnumber,type:'inputs'}))
-      divin.style.cursor = 'default'
-      divin.addEventListener('mousedown',nothing)
-      divin.addEventListener('touchstart',nothing)
-      divin.addEventListener('mouseup',nothing)
-      divin.addEventListener('touchend',nothing)
-      for (var v in args.inputs) {
-         var div = document.createElement('div')
-         if (args.inputs[v].label != undefined)
-            div.innerHTML += args.inputs[v].label
-         else
-            div.innerHTML += v
-         if (args.inputs[v].type != '')
-            div.innerHTML += ' ('+args.inputs[v].type+')'
-         div.setAttribute('id',JSON.stringify({id:idnumber,type:'inputs',name:v}))
-         div.addEventListener('mouseover',input_over)
-         div.addEventListener('mouseout',input_out)
-         div.addEventListener('mousedown',input_mousedown)
-         div.addEventListener('touchstart',input_touchdown)
-         div.dataset.links = JSON.stringify([])
-         div.dataset.name = v
-         divin.appendChild(div)
-         div.dataset.id = JSON.stringify(
-            {id:idnumber,type:'input',name:v,
-            rnd:Math.random()}) // randomize for unique events
-         window.addEventListener(div.dataset.id,args.inputs[v].event)
-         }
-      container.appendChild(divin)
-      if ((Object.keys(args.inputs).length) == 0)
-         divin.style.visibility = 'hidden'
-      divin.style.left = divname.clientWidth/2
-         -divint.clientWidth/2-divin.clientWidth
-      for (var i = 1; i < divin.childNodes.length; ++i) {
-         divin.childNodes[i].dataset.dx = divin.offsetLeft
-            +divin.childNodes[i].offsetLeft
-         divin.childNodes[i].dataset.dy = divin.offsetTop
-            +divin.childNodes[i].offsetTop
-            +divin.childNodes[i].offsetHeight/2
-         }
-   //
-   // outputs
-   //
-   var divout = document.createElement('div')
-      divout.setAttribute('id',JSON.stringify({id:idnumber,type:'outputs'}))
-      var b = document.createElement('b')
-         b.appendChild(document.createTextNode('outputs'))
-      divout.appendChild(b)
-      divout.style.backgroundColor = "rgb(240,240,210)"
-      divout.style.padding = mods.ui.padding
-      divout.style.paddingRight = '2px'
-      divout.style.position = "absolute"
-      divout.style.top = divname.clientHeight+divctrl.clientHeight
-      divout.style.textAlign = 'right'
-      divout.addEventListener('mousedown',nothing)
-      divout.style.border = '2px solid'
-      divout.style.borderRadius = '10px'
-      divout.setAttribute('id',JSON.stringify({id:idnumber,type:'outputs'}))
-      divout.style.cursor = 'default'
-      divout.addEventListener('touchstart',nothing)
-      divout.addEventListener('mouseup',nothing)
-      divout.addEventListener('touchend',nothing)
-      for (var v in args.outputs) {
-         var div = document.createElement('div')
-         if (args.outputs[v].label != undefined)
-            div.innerHTML += args.outputs[v].label
-         else
-            div.innerHTML += v
-         if (args.outputs[v].type != '')
-            div.innerHTML += ' ('+args.outputs[v].type+')'
-         div.setAttribute('id',JSON.stringify({id:idnumber,type:'outputs',name:v}))
-         div.addEventListener('mouseover',output_over)
-         div.addEventListener('mouseout',output_out)
-         div.addEventListener('mousedown',output_mousedown)
-         div.addEventListener('touchstart',output_touchdown)
-         div.dataset.links = JSON.stringify([])
-         div.dataset.name = v
-         divout.appendChild(div)
-         div.dataset.id = JSON.stringify(
-            {id:idnumber,type:'output',name:v,
-            rnd:Math.random()}) // randomize for unique events
-         window.addEventListener(div.dataset.id,args.outputs[v].event)
-         }
-      container.appendChild(divout)
-      if ((Object.keys(args.outputs).length) == 0)
-         divout.style.visibility = 'hidden'
-      divout.style.left = divname.clientWidth/2
-         +divint.clientWidth/2
-      for (var i = 1; i < divout.childNodes.length; ++i) {
-         divout.childNodes[i].dataset.dx = divout.offsetLeft
-            +divout.childNodes[i].offsetLeft
-            +divout.childNodes[i].offsetWidth
-         divout.childNodes[i].dataset.dy = divout.offsetTop
-            +divout.childNodes[i].offsetTop
-            +divout.childNodes[i].offsetHeight/2
-         }
-      //
-      // initialization
-      //
-      args.init()
-      //
-      // resize to contents
-      //
-      container.style.width = divint.clientWidth+divin.clientWidth+divout.clientWidth
-      mods.fit(divint)
-      //
-      // return container
-      //
-      return(container)
-   }
-function delete_module(idnumber) {
-   //
-   // delete links
-   //
-   var ins = document.getElementById(
-      JSON.stringify({id:idnumber,type:'inputs'}))
-   var outs = document.getElementById(
-      JSON.stringify({id:idnumber,type:'outputs'}))
-   for (var i = 1; i < ins.childNodes.length; ++i) {
-      var links = JSON.parse(ins.childNodes[i].dataset.links)
-      for (var l in links)
-         delete_link(links[l])
-      }
-   for (var i = 1; i < outs.childNodes.length; ++i) {
-      var links = JSON.parse(outs.childNodes[i].dataset.links)
-      for (var l in links)
-         delete_link(links[l])
-      }
-   //
-   // delete container
-   //
-   var modules = document.getElementById('modules')
-   var container = document.getElementById(idnumber)
-   modules.removeChild(container)
-   //
-   // clear prompt
-   //
-   set_prompt('')
-   }
-function update_module_definition(id) {
-   //
-   // get definition
-   //
-   var module = document.getElementById(id)
-   var def = module.dataset.definition
-   //
-   // check for mod
-   //
-   if (mods.mod[id] == undefined)
-      return def
-   //
-   // split definition
-   //
-   var lines = def.split('\n')
-   //
-   // find init function
-   //
-   var line = 0
-   while (line < lines.length) {
-      if (lines[line].indexOf("var init") == 0)
-         break
-      line += 1
-      }
-   //
-   // read initializations up to inputs function
-   //
-   while (line < lines.length) {
-      if (lines[line].indexOf(".value =") != -1) {
-         var start = 4+lines[line].indexOf("mod.")
-         var end = lines[line].indexOf(".value")
-         var key = lines[line].slice(start,end)
-         var value = mods.mod[id][key]['value']
-         if (value.indexOf('\n') != -1)
-            value = value.replace(/\n/g,"\\n")
-         lines[line] = "   mod."+key+".value = '"+value+"'"
-         }
-      else if (lines[line].indexOf(".checked =") != -1) {
-         var start = 4+lines[line].indexOf("mod.")
-         var end = lines[line].indexOf(".checked")
-         var key = lines[line].slice(start,end)
-         var value = mods.mod[id][key]['checked']
-         lines[line] = "   mod."+key+".checked = "+value
-         }
-      if (lines[line].indexOf("var inputs") == 0)
-         break
-      line += 1
-      }
-   return(lines.join('\n'))
-   }
-function edit_module(evt) {
-   var mod = evt.target.parentNode.parentNode
-   var idnumber = mod.id
-   var def = update_module_definition(idnumber)
-   //
-   // open edit window
-   //
-   var top = mod.dataset.top
-   var left = mod.dataset.left
-   var name = mod.dataset.name
-   var filename = mod.dataset.filename
-   var fontsize = 100
-   var win = window.open('')
-   var file = document.createElement('input')
-      file.setAttribute('type','file')
-      file.setAttribute('id','edit_module_file')
-      file.style.position = 'absolute'
-      file.style.left = 0
-      file.style.top = 0
-      file.style.width = 0
-      file.style.height = 0
-      file.style.opacity = 0
-      file.addEventListener('change',function() {
-         edit_module_read_handler()
-         })
-      win.document.body.appendChild(file)
-   function edit_module_read_handler() {
-      var file = win.document.getElementById('edit_module_file')
-      var file_reader = new FileReader()
-      file_reader.onload = edit_module_load_handler
-      file_reader.readAsText(file.files[0])
-      }
-   function edit_module_load_handler(event) {
-      str = event.target.result
-      var text = win.document.getElementById('edit_module_text')
-      text.value = str
-      update_module(idnumber)
-      win.close()
-      }
-   if (filename != "undefined" && filename != "") {
-      var btn = document.createElement('button')
-         btn.appendChild(document.createTextNode('reload from server'))
-         btn.style.padding = mods.ui.padding
-         btn.style.margin = 1
-         btn.addEventListener('click',function(){
-            var req = new XMLHttpRequest()
-            req.responseType = 'text'
-            req.onreadystatechange = function() {
-               if (req.readyState == XMLHttpRequest.DONE) {
-                  text.value = req.response
-                  update_module(idnumber)
-                  }
-               }
-            req.open('GET',filename+'?rnd='+Math.random())
-            req.send()
-            win.close()
-            })
-         win.document.body.appendChild(btn)
-      }
-   var btn = document.createElement('button')
-      btn.appendChild(document.createTextNode('load from file'))
-      btn.style.padding = mods.ui.padding
-      btn.style.margin = 1
-      btn.addEventListener('click',function(){
-         var file = win.document.getElementById('edit_module_file')
-         file.value = null
-         file.click()
-         })
-      win.document.body.appendChild(btn)
-   var btn = document.createElement('button')
-      btn.appendChild(document.createTextNode('update and close'))
-      btn.style.padding = mods.ui.padding
-      btn.style.margin = 1
-      btn.addEventListener('click',function(){
-         update_module(idnumber)
-         win.close()
-         })
-      win.document.body.appendChild(btn)
-   var btn = document.createElement('button')
-      btn.appendChild(document.createTextNode('update'))
-      btn.style.padding = mods.ui.padding
-      btn.style.margin = 1
-      btn.addEventListener('click',function(){
-         update_module(idnumber)
-         })
-      win.document.body.appendChild(btn)
-   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)
-   var btn = document.createElement('button')
-      btn.appendChild(document.createTextNode('save'))
-      btn.style.padding = mods.ui.padding
-      btn.style.margin = 1
-      btn.addEventListener('click',function(){
-         var a = document.createElement('a')
-         a.setAttribute('href','data:text/plain;charset=utf-8,'+
-            encodeURIComponent(text.value))
-         a.setAttribute('download',name)
-         a.style.display = 'none'
-         document.body.appendChild(a)
-         a.click()
-         document.body.removeChild(a)
-         })
-      win.document.body.appendChild(btn)
-   var btn = document.createElement('button')
-      btn.appendChild(document.createTextNode('increase font'))
-      btn.style.padding = mods.ui.padding
-      btn.style.margin = 1
-      btn.addEventListener('click',function(){
-         fontsize *= 1.2
-         text.style.fontSize = fontsize+'%'
-         })
-      win.document.body.appendChild(btn)
-   var btn = document.createElement('button')
-      btn.appendChild(document.createTextNode('decrease font'))
-      btn.style.padding = mods.ui.padding
-      btn.style.margin = 1
-      btn.addEventListener('click',function(){
-         fontsize /= 1.2
-         text.style.fontSize = fontsize+'%'
-         })
-      win.document.body.appendChild(btn)
-   win.document.body.appendChild(document.createElement('br'))
-   var text = document.createElement('textarea')
-      text.setAttribute('id','edit_module_text')
-      text.style.width = '100%'
-      text.style.height= '100%'
-      text.value = def
-      win.document.body.appendChild(text)
-   function reload_module(idnumber) {
-      }
-   function update_module(idnumber) {
-      //
-      // save links
-      //
-      var ins = document.getElementById(
-         JSON.stringify({id:idnumber,type:'inputs'}))
-      var inlinks = []
-      for (var i = 1; i < ins.childNodes.length; ++i) {
-         var links = JSON.parse(ins.childNodes[i].dataset.links)
-         for (var l in links)
-            inlinks.push(links[l])
-         }
-      var outs = document.getElementById(
-         JSON.stringify({id:idnumber,type:'outputs'}))
-      var outlinks = []
-      for (var i = 1; i < outs.childNodes.length; ++i) {
-         var links = JSON.parse(outs.childNodes[i].dataset.links)
-         for (var l in links)
-            outlinks.push(links[l])
-         }
-      //
-      // delete module
-      //
-      delete_module(idnumber)
-      //
-      // add module
-      //
-      var def = text.value
-      try {
-         eval('var args = '+def)
-         }
-      catch (err) {
-         console.log(err.message)
-         return
-         }
-      args.definition = def
-      args.id = idnumber
-      args.top = top
-      args.left = left
-      args.filename = filename
-      add_module(args)
-      //
-      // add links
-      //
-      for (var l in inlinks) {
-         eval('var link = '+inlinks[l])
-         eval('var linksrc = '+link.source)
-         eval('var linkdst = '+link.dest)
-         var src = document.getElementById(
-            JSON.stringify(
-               {id:linksrc.id,type:linksrc.type,name:linksrc.name}))
-         var dst = document.getElementById(
-            JSON.stringify(
-               {id:linkdst.id,type:linkdst.type,name:linkdst.name}))
-         add_link(src,dst)
-         }
-      for (var l in outlinks) {
-         eval('var link = '+outlinks[l])
-         eval('var linksrc = '+link.source)
-         eval('var linkdst = '+link.dest)
-         var src = document.getElementById(
-            JSON.stringify(
-               {id:linksrc.id,type:linksrc.type,name:linksrc.name}))
-         var dst = document.getElementById(
-            JSON.stringify(
-               {id:linkdst.id,type:linkdst.type,name:linkdst.name}))
-         add_link(src,dst)
-         }
-      }
-   }
-//
-// UI routines
-//
-function set_prompt(txt) {
-   var span = document.getElementById('prompt')
-   span.childNodes[0].innerHTML = ' '+txt
-   }
-function get_prompt(txt,fn) {
-   var div = document.getElementById('prompt')
-   if (div.childNodes.length > 2)
-      //
-      // already getting a prompt
-      //
-      return
-   var text = document.createElement('input')
-      text.type = 'text'
-      text.size = 20
-      text.value = txt
-      text.addEventListener('keydown',function(evt){
-         if (evt.key == 'Enter') {
-            var div = document.getElementById('prompt')
-            div.removeChild(div.childNodes[1])
-            div.removeChild(div.childNodes[1])
-            set_prompt('')
-            fn(text.value)
-            }
-         })
-      div.appendChild(text)
-      div.appendChild(document.createTextNode(' (enter)'))
-      text.focus()
-   }
-function nothing(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   }
-//
-// link routines
-//
-mods.add_link = function(src,dst) {
-   add_link(src,dst)
-   }
-function add_link(src,dst) {
-   //
-   // link order from out to in
-   //
-   if (src.id.indexOf('outputs') == -1) {
-      var tmp = src
-      src = dst
-      dst = tmp
-      }
-   //
-   // check if link exists
-   //
-   var id = JSON.stringify({source:src.id,dest:dst.id})
-   var link = document.getElementById(id)
-   if (link != null) {
-      //
-      // yes, remove it and return
-      //
-      delete_link(id)
-      return
-      }
-   //
-   // no, add link
-   //
-   var links = JSON.parse(src.dataset.links)
-      links.push(id)
-      src.dataset.links = JSON.stringify(links)
-   var links = JSON.parse(dst.dataset.links)
-      links.push(id)
-      dst.dataset.links = JSON.stringify(links)
-   //
-   // draw link
-   //
-   xsrc = src.parentNode.parentNode.offsetLeft
-      +parseFloat(src.dataset.dx)
-   ysrc = src.parentNode.parentNode.offsetTop
-      +parseFloat(src.dataset.dy)
-      -mods.ui.header
-   xdst = dst.parentNode.parentNode.offsetLeft
-      +parseFloat(dst.dataset.dx)
-   ydst = dst.parentNode.parentNode.offsetTop
-      +parseFloat(dst.dataset.dy)
-      -mods.ui.header
-   var links = document.getElementById('links')
-   var path = document.createElementNS('http://www.w3.org/2000/svg','path')
-      path.setAttribute('id',id)
-      path.setAttribute('d','M'+xsrc+','+ysrc+' C'+(xsrc+mods.ui.bezier)+','
-         +ysrc+' '+(xdst-mods.ui.bezier)+','+ydst+' '+xdst+','+ydst)
-      path.setAttribute('fill','none')
-      path.setAttribute('stroke','rgb(0,0,128)')
-      path.setAttribute('stroke-width',3)
-      links.appendChild(path)
-   /*
-   //
-   // don't trigger link
-   //
-   eval('var evtid = '+src.id)
-   evtid.type = 'output'
-   var evtstr = JSON.stringify(evtid)
-   var evt = new CustomEvent(evtstr)
-   window.dispatchEvent(evt)
-   */
-   }
-function delete_link(linkid) {
-   //
-   // delete a link
-   //
-   var links = document.getElementById('links')
-   links.removeChild(document.getElementById(linkid))
-   link = JSON.parse(linkid)
-   src = document.getElementById(link.source)
-   dst = document.getElementById(link.dest)
-   var links = JSON.parse(src.dataset.links)
-      var index = links.indexOf(linkid)
-      links.splice(index,1)
-      src.dataset.links = JSON.stringify(links)
-   var links = JSON.parse(dst.dataset.links)
-      var index = links.indexOf(linkid)
-      links.splice(index,1)
-      dst.dataset.links = JSON.stringify(links)
-   }
-function draw_links(idnumber,color) {
-   //
-   // draw a module's links
-   //
-   var ins = document.getElementById(
-      JSON.stringify({id:idnumber,type:'inputs'}))
-   var outs = document.getElementById(
-      JSON.stringify({id:idnumber,type:'outputs'}))
-   for (var i = 1; i < ins.childNodes.length; ++i) {
-      var links = JSON.parse(ins.childNodes[i].dataset.links)
-      for (var l in links)
-         draw_link(links[l],color)
-      }
-   for (var i = 1; i < outs.childNodes.length; ++i) {
-      var links = JSON.parse(outs.childNodes[i].dataset.links)
-      for (var l in links)
-         draw_link(links[l],color)
-      }
-   }
-function draw_link(id,color) {
-   //
-   // draw a link
-   //
-   var link = JSON.parse(id)
-   src = document.getElementById(link.source)
-   dst = document.getElementById(link.dest)
-   var path = document.getElementById(id)
-   xsrc = src.parentNode.parentNode.offsetLeft
-      +parseFloat(src.dataset.dx)
-   ysrc = src.parentNode.parentNode.offsetTop
-      +parseFloat(src.dataset.dy)
-      -mods.ui.header
-   xdst = dst.parentNode.parentNode.offsetLeft
-      +parseFloat(dst.dataset.dx)
-   ydst = dst.parentNode.parentNode.offsetTop
-      +parseFloat(dst.dataset.dy)
-      -mods.ui.header
-   path.setAttribute('d','M'+xsrc+','+ysrc+' C'+(xsrc+mods.ui.bezier)+','
-      +ysrc+' '+(xdst-mods.ui.bezier)+','+ydst+' '+xdst+','+ydst)
-   path.setAttribute('stroke',color)
-   }
-//
-// mods routines to be called from modules
-//
-mods.fit = function(div) {
-   //
-   // fit a module
-   //
-   div.style.left = div.dataset.divNameSize/2-div.clientWidth/2
-   var divin = document.getElementById(
-      JSON.stringify(
-         {id:div.dataset.id,type:'inputs'}))
-   divin.style.left = div.dataset.divNameSize/2-div.clientWidth/2-divin.clientWidth
-   var divout = document.getElementById(
-      JSON.stringify(
-         {id:div.dataset.id,type:'outputs'}))
-   divout.style.left = div.dataset.divNameSize/2+div.clientWidth/2
-   }
-mods.output = function(mod,varname,val) {
-   //
-   // send module outputs
-   //
-   var div = mod.div
-   var key = JSON.parse(div.id)
-   var idnumber = key.id
-   var out = document.getElementById(
-      JSON.stringify(
-         {id:idnumber,type:'outputs',name:varname}))
-   var links = JSON.parse(out.dataset.links)
-   for (var l in links) {
-      var link = JSON.parse(links[l])
-      var dest = JSON.parse(link.dest)
-      var divin = document.getElementById(JSON.stringify(
-         {id:dest.id,type:'inputs',name:dest.name}))
-      var evt = new CustomEvent(divin.dataset.id,{detail:val})
-      window.dispatchEvent(evt)
-      }
-   }
-//
-// module mod-ification calls
-//
-mods.module_create = function(args) {
-   var event = {target:{result:args}}
-   var div = mod_load_handler(event)
-   return(div)
-   }
-mods.module_delete = function(id) {
-   delete_module(id)
-   }
-mods.module_id = function(div) {
-   return div.parentNode.id
-   }
-mods.module_left = function(id) {
-   var module = document.getElementById(id)
-   return (parseInt(module.style.left))
-   }
-mods.module_move = function(id,dx,dy) {
-   var module = document.getElementById(id)
-   var top = parseInt(module.style.top)
-   module.style.top = top+dy
-   module.dataset.top = top+dy
-   var left = parseInt(module.style.left)
-   module.style.left = left+dx
-   module.dataset.left = left+dx
-   draw_links(id,mods.ui.link_color)
-   }
-mods.module_inputs = function(id,index) {
-   var module = document.getElementById(id)
-   var inputs = document.getElementById(
-      JSON.stringify({id:id,type:'inputs'}))
-   console.log(inputs.childNodes[index])
-   }
-mods.module_outputs = function(id,index) {
-   var module = document.getElementById(id)
-   var outputs = document.getElementById(
-      JSON.stringify({id:id,type:'outputs'}))
-   console.log(outputs.childNodes[index])
-   }
-mods.module_position = function(id,x,y) {
-   var module = document.getElementById(id)
-   var top = parseInt(module.style.top)
-   module.style.top = y
-   module.dataset.top = y
-   var left = parseInt(module.style.left)
-   module.style.left = x
-   module.dataset.left = x
-   draw_links(id,mods.ui.link_color)
-   }
-mods.module_top = function(id) {
-   var module = document.getElementById(id)
-   return (parseInt(module.style.top))
-   }
-mods.module_width = function(id) {
-   var module = document.getElementById(id)
-   return (parseInt(module.clientWidth))
-   }
-//
-// input event handlers
-//
-function input_over(evt) {
-   evt.target.style.fontWeight = 'bold'
-   var links = JSON.parse(evt.target.dataset.links)
-   for (var l in links)
-      draw_link(links[l],mods.ui.link_highlight)
-   if (mods.ui.source == null)
-      set_prompt('click to link')
-   }
-function input_out(evt) {
-   evt.target.style.fontWeight = 'normal'
-   var links = JSON.parse(evt.target.dataset.links)
-   for (var l in links)
-      draw_link(links[l],mods.ui.link_color)
-   if (mods.ui.source == null)
-      set_prompt('')
-   }
-function input_mousedown(evt) {
-   if (mods.ui.source == null) {
-      mods.ui.source = evt.target
-      set_prompt('variable to link/unlink to?')
-      }
-   else {
-      add_link(mods.ui.source,evt.target)
-      set_prompt('')
-      mods.ui.source = null
-      }
-   }
-function input_touchdown(evt) {
-   if (mods.ui.source == null) {
-      mods.ui.source = evt.target
-      set_prompt('variable to link/unlink to?')
-      }
-   else {
-      add_link(mods.ui.source,evt.target)
-      set_prompt('')
-      mods.ui.source = null
-      }
-   }
-//
-// output event handlers
-//
-function output_over(evt) {
-   evt.target.style.fontWeight = 'bold'
-   var links = JSON.parse(evt.target.dataset.links)
-   for (var l in links)
-      draw_link(links[l],mods.ui.link_highlight)
-   if (mods.ui.source == null)
-      set_prompt('click to link')
-   }
-function output_out(evt) {
-   evt.target.style.fontWeight = 'normal'
-   var links = JSON.parse(evt.target.dataset.links)
-   for (var l in links)
-      draw_link(links[l],mods.ui.link_color)
-   if (mods.ui.source == null)
-      set_prompt('')
-   }
-function output_mousedown(evt) {
-   if (mods.ui.source == null) {
-      mods.ui.source = evt.target
-      set_prompt('variable to link/unlink to?')
-      }
-   else {
-      add_link(mods.ui.source,evt.target)
-      set_prompt('')
-      mods.ui.source = null
-      }
-   }
-function output_touchdown(evt) {
-   if (mods.ui.source == null) {
-      mods.ui.source = evt.target
-      set_prompt('variable to link/unlink to?')
-      }
-   else {
-      add_link(mods.ui.source,evt.target)
-      set_prompt('')
-      mods.ui.source = null
-      }
-   }
-//
-// name event handlers
-//
-function name_over(evt) {
-   evt.target.style.fontWeight = 'bold'
-   if (mods.ui.source == null)
-      set_prompt('click and drag to move')
-   }
-function name_out(evt) {
-   evt.target.style.fontWeight = 'normal'
-   if (mods.ui.source == null)
-      set_prompt('')
-   }
-function name_mousedown(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   var div = document.getElementById(evt.target.parentNode.id)
-      div.style.zIndex = 1
-      mods.ui.xstart = evt.clientX
-      mods.ui.ystart = evt.clientY
-      mods.ui.selected[evt.target.parentNode.id] = true
-      window.addEventListener('mousemove',name_mousemove)
-      window.addEventListener('mouseup',name_mouseup)
-   }
-function name_mousemove(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   var t = mods_transform()
-   for (var id in mods.ui.selected) {
-      var div = document.getElementById(id)
-         var dx = (evt.clientX-mods.ui.xstart)/t.s
-         var dy = (evt.clientY-mods.ui.ystart)/t.s
-         var newleft = parseFloat(div.dataset.left)+dx
-         var newtop = parseFloat(div.dataset.top)+dy
-         div.style.left = newleft+'px'
-         div.style.top = newtop+'px'
-      draw_links(id,mods.ui.link_color)
-      }
-   }
-function name_mouseup(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   var t = mods_transform()
-   for (var id in mods.ui.selected) {
-      var div = document.getElementById(id)
-         div.style.zIndex = 0
-         div.childNodes[0].style.fontWeight = 'normal'
-         var dx = (evt.clientX-mods.ui.xstart)/t.s
-         var dy = (evt.clientY-mods.ui.ystart)/t.s
-         div.dataset.left = parseFloat(div.dataset.left)+dx
-         div.dataset.top = parseFloat(div.dataset.top)+dy
-         window.removeEventListener('mousemove',name_mousemove)
-         window.removeEventListener('mouseup',name_mouseup)
-      }
-   mods.ui.selected = {}
-   }
-function name_touchdown(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   var div = document.getElementById(evt.target.parentNode.id)
-      div.style.zIndex = 1
-      mods.ui.xstart = evt.changedTouches[0].pageX
-      mods.ui.ystart = evt.changedTouches[0].pageY
-      mods.ui.selected[evt.target.parentNode.id] = true
-      window.addEventListener('touchmove',name_touchmove)
-      window.addEventListener('touchend',name_touchup)
-   }
-function name_touchmove(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   var t = mods_transform()
-   for (var id in mods.ui.selected) {
-      var div = document.getElementById(id)
-         var dx = (evt.changedTouches[0].pageX-mods.ui.xstart)/t.s
-         var dy = (evt.changedTouches[0].pageY-mods.ui.ystart)/t.s
-         var newleft = parseFloat(div.dataset.left)+dx
-         var newtop = parseFloat(div.dataset.top)+dy
-         div.style.left = newleft+'px'
-         div.style.top = newtop+'px'
-      draw_links(id,mods.ui.link_color)
-      }
-   }
-function name_touchup(evt) {
-   evt.preventDefault()
-   evt.stopPropagation()
-   var t = mods_transform()
-   for (var id in mods.ui.selected) {
-      var div = document.getElementById(id)
-         div.style.zIndex = 0
-         var dx = (evt.changedTouches[0].pageX-mods.ui.xstart)/t.s
-         var dy = (evt.changedTouches[0].pageY-mods.ui.ystart)/t.s
-         div.dataset.left = parseFloat(div.dataset.left)+dx
-         div.dataset.top = parseFloat(div.dataset.top)+dy
-         window.removeEventListener('touchmove',name_touchmove)
-         window.removeEventListener('touchend',name_touchup)
-      }
-   mods.ui.selected = {}
-   }
-})()
-var prog = JSON.parse(JSON.stringify({"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"457.58350726962306","left":"3676.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1074.583507269623","left":"4134.994408259125","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"944.5835072696229","left":"3703.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"557.583507269623","left":"4135.994408259125","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"590.583507269623","left":"3164.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1294.583507269623","left":"2494.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"515.583507269623","left":"2517.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1060.583507269623","left":"3208.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.0312'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.010'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.072'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"435.58350726962306","left":"1973.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.8622681794886853":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1282.9473065265367","left":"2013.9337776993061","filename":"modules/file/save","inputs":{},"outputs":{}},"0.057765477464730264":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"399.03488793999406","left":"1166.572396970906","filename":"modules/read/svg","inputs":{},"outputs":{}},"0.5022735462618486":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '1000'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"505.69493521948414","left":"1540.852199242571","filename":"modules/convert/svg/image","inputs":{},"outputs":{}},"0.9604471250218796":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//           - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '2.5'\n   mod.plungespeed.value = '2.5'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '11000'\n   mod.tool.value = '1'\n   mod.coolantoff.checked = true\n   mod.formatMm.checked = true\n   mod.unitMinutes.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // inch or mm format\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n   div.appendChild(document.createElement('br'))\n   //\n   // second or minute rate units \n   //\n   div.appendChild(document.createTextNode('rate units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitSeconds'\n      input.checked = true\n      div.appendChild(input)\n      mod.unitSeconds = input\n   div.appendChild(document.createTextNode('second'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitMinutes'\n      div.appendChild(input)\n      mod.unitMinutes = input\n   div.appendChild(document.createTextNode('minute'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_height /= 25.4\n      }\n   if (mod.unitMinutes.checked) {\n      cut_speed *= 60\n      plunge_speed *= 60\n      }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n //str += \"G04 P1000\\n\" // give spindle 1 second to spin up................comment out\n   //str += \"G04 P1\\n\" // give spindle 1 Millisecond to spin up..............comment out\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"831.1683166499946","left":"1985.839479173399","filename":"modules/path/formats/g-code","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.057765477464730264\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8622681794886853\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}))
+
+<!doctype html>
+<html lang="en" class="no-js">
+  <head>
+    
+      <meta charset="utf-8">
+      <meta name="viewport" content="width=device-width,initial-scale=1">
+      
+        <meta name="description" content="Fab Academy documentation site for FabLab Kannai">
+      
+      
+        <meta name="author" content="Yuichi TAMIYA">
+      
+      
+      <link rel="icon" href="/2022/labs/kannai/images/favicon.svg">
+      <meta name="generator" content="mkdocs-1.2.3, mkdocs-material-8.1.11">
+    
+    
+      
+        <title>FabLab Kannai - Fab Academy 2022</title>
+      
+    
+    
+      <link rel="stylesheet" href="/2022/labs/kannai/assets/stylesheets/main.50e68009.min.css">
+      
+        
+        <link rel="stylesheet" href="/2022/labs/kannai/assets/stylesheets/palette.e6a45f82.min.css">
+        
+      
+    
+    
+    
+      
+        
+        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,400i,700%7CUbuntu+Mono&display=fallback">
+        <style>:root{--md-text-font:"Ubuntu";--md-code-font:"Ubuntu Mono"}</style>
+      
+    
+    
+      <link rel="stylesheet" href="/2022/labs/kannai/stylesheets/extra.css">
+    
+    <script>__md_scope=new URL("/2022/labs/kannai/",location),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
+    
+      
+
+    
+    
+  </head>
+  
+  
+    
+    
+    
+    
+    
+    <body dir="ltr" data-md-color-scheme="" data-md-color-primary="none" data-md-color-accent="none">
+  
+    
+    
+    <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
+    <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
+    <label class="md-overlay" for="__drawer"></label>
+    <div data-md-component="skip">
+      
+    </div>
+    <div data-md-component="announce">
+      
+    </div>
+    
+    
+      
+
+<header class="md-header" data-md-component="header">
+  <nav class="md-header__inner md-grid" aria-label="Header">
+    <a href="/2022/labs/kannai/." title="FabLab Kannai - Fab Academy 2022" class="md-header__button md-logo" aria-label="FabLab Kannai - Fab Academy 2022" data-md-component="logo">
+      
+  
+  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 3 1 9l11 6 9-4.91V17h2V9M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82z"/></svg>
+
+    </a>
+    <label class="md-header__button md-icon" for="__drawer">
+      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/></svg>
+    </label>
+    <div class="md-header__title" data-md-component="header-title">
+      <div class="md-header__ellipsis">
+        <div class="md-header__topic">
+          <span class="md-ellipsis">
+            FabLab Kannai - Fab Academy 2022
+          </span>
+        </div>
+        <div class="md-header__topic" data-md-component="header-topic">
+          <span class="md-ellipsis">
+            
+              
+            
+          </span>
+        </div>
+      </div>
+    </div>
+    
+    
+    
+      <label class="md-header__button md-icon" for="__search">
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
+      </label>
+      <div class="md-search" data-md-component="search" role="dialog">
+  <label class="md-search__overlay" for="__search"></label>
+  <div class="md-search__inner" role="search">
+    <form class="md-search__form" name="search">
+      <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
+      <label class="md-search__icon md-icon" for="__search">
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
+      </label>
+      <nav class="md-search__options" aria-label="Search">
+        
+        <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
+          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
+        </button>
+      </nav>
+      
+    </form>
+    <div class="md-search__output">
+      <div class="md-search__scrollwrap" data-md-scrollfix>
+        <div class="md-search-result" data-md-component="search-result">
+          <div class="md-search-result__meta">
+            Initializing search
+          </div>
+          <ol class="md-search-result__list"></ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+    
+    
+  </nav>
+  
+</header>
+    
+    <div class="md-container" data-md-component="container">
+      
+      
+        
+          
+            
+<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
+  <div class="md-tabs__inner md-grid">
+    <ul class="md-tabs__list">
+      
+        
+  
+  
+
+
+  <li class="md-tabs__item">
+    <a href="/2022/labs/kannai/." class="md-tabs__link">
+      Fab Academy 2022 Fab Lab Kannai Site
+    </a>
+  </li>
+
+      
+        
+  
+  
+
+
+  
+  
+  
+    <li class="md-tabs__item">
+      <a href="/2022/labs/kannai/Instruction/tips_list/" class="md-tabs__link">
+        Instruction
+      </a>
+    </li>
+  
+
+      
+        
+  
+  
+
+
+  
+  
+  
+    <li class="md-tabs__item">
+      <a href="/2022/labs/kannai/Machine_Building/machine-building/" class="md-tabs__link">
+        Machine Building
+      </a>
+    </li>
+  
+
+      
+        
+  
+  
+
+
+  
+  
+  
+    <li class="md-tabs__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week01/" class="md-tabs__link">
+        Weekly Group Assignments
+      </a>
+    </li>
+  
+
+      
+        
+  
+  
+
+
+  
+  
+  
+    <li class="md-tabs__item">
+      <a href="/2022/labs/kannai/about_my_lab/" class="md-tabs__link">
+        About my lab
+      </a>
+    </li>
+  
+
+      
+    </ul>
+  </div>
+</nav>
+          
+        
+      
+      <main class="md-main" data-md-component="main">
+        <div class="md-main__inner md-grid">
+          
+            
+              
+              <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
+                <div class="md-sidebar__scrollwrap">
+                  <div class="md-sidebar__inner">
+                    
+
+  
+
+
+<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
+  <label class="md-nav__title" for="__drawer">
+    <a href="/2022/labs/kannai/." title="FabLab Kannai - Fab Academy 2022" class="md-nav__button md-logo" aria-label="FabLab Kannai - Fab Academy 2022" data-md-component="logo">
+      
+  
+  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 3 1 9l11 6 9-4.91V17h2V9M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82z"/></svg>
+
+    </a>
+    FabLab Kannai - Fab Academy 2022
+  </label>
+  
+  <ul class="md-nav__list" data-md-scrollfix>
+    
+      
+      
+      
+
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/." class="md-nav__link">
+        Fab Academy 2022 Fab Lab Kannai Site
+      </a>
+    </li>
+  
+
+    
+      
+      
+      
+
+  
+  
+  
+    
+    <li class="md-nav__item md-nav__item--nested">
+      
+      
+        <input class="md-nav__toggle md-toggle" data-md-toggle="__nav_2" type="checkbox" id="__nav_2" >
+      
+      
+      
+      
+        <label class="md-nav__link" for="__nav_2">
+          Instruction
+          <span class="md-nav__icon md-icon"></span>
+        </label>
+      
+      <nav class="md-nav" aria-label="Instruction" data-md-level="1">
+        <label class="md-nav__title" for="__nav_2">
+          <span class="md-nav__icon md-icon"></span>
+          Instruction
+        </label>
+        <ul class="md-nav__list" data-md-scrollfix>
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/tips_list/" class="md-nav__link">
+        Tips list
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week01/" class="md-nav__link">
+        1. Principles and practices / Project management
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week02/" class="md-nav__link">
+        2. Computer Aided design
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week03/" class="md-nav__link">
+        3. Computer controlled cutting
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week04/" class="md-nav__link">
+        4. Electronics production
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week05/" class="md-nav__link">
+        5. 3D Scanning and printing
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week06/" class="md-nav__link">
+        6. Electronics design
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week07/" class="md-nav__link">
+        7. Computer controlled machining
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week08/" class="md-nav__link">
+        8. Embedded programming
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week09/" class="md-nav__link">
+        9. Molding and casting
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week10/" class="md-nav__link">
+        10. Output devices
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week11/" class="md-nav__link">
+        11. Mechanical design / Machine design
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week12/" class="md-nav__link">
+        12. Input devices
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week13/" class="md-nav__link">
+        13. Networking and communications
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week14/" class="md-nav__link">
+        14. Interface and application programming
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week15/" class="md-nav__link">
+        15. Wildcard week
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week16/" class="md-nav__link">
+        16. Applications and implications
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week17/" class="md-nav__link">
+        17. Invention, intellectual property and income
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/week18/" class="md-nav__link">
+        18. Project development
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    
+    <li class="md-nav__item md-nav__item--nested">
+      
+      
+        <input class="md-nav__toggle md-toggle" data-md-toggle="__nav_2_20" type="checkbox" id="__nav_2_20" >
+      
+      
+      
+      
+        <label class="md-nav__link" for="__nav_2_20">
+          Tips
+          <span class="md-nav__icon md-icon"></span>
+        </label>
+      
+      <nav class="md-nav" aria-label="Tips" data-md-level="2">
+        <label class="md-nav__title" for="__nav_2_20">
+          <span class="md-nav__icon md-icon"></span>
+          Tips
+        </label>
+        <ul class="md-nav__list" data-md-scrollfix>
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/tips/genmitsu_prover_xl_setup/" class="md-nav__link">
+        Genmitsu PROVerXL 4030
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Instruction/tips/make_this_site/" class="md-nav__link">
+        How to make this site
+      </a>
+    </li>
+  
+
+            
+          
+        </ul>
+      </nav>
+    </li>
+  
+
+            
+          
+        </ul>
+      </nav>
+    </li>
+  
+
+    
+      
+      
+      
+
+  
+  
+  
+    
+    <li class="md-nav__item md-nav__item--nested">
+      
+      
+        <input class="md-nav__toggle md-toggle" data-md-toggle="__nav_3" type="checkbox" id="__nav_3" >
+      
+      
+      
+      
+        <label class="md-nav__link" for="__nav_3">
+          Machine Building
+          <span class="md-nav__icon md-icon"></span>
+        </label>
+      
+      <nav class="md-nav" aria-label="Machine Building" data-md-level="1">
+        <label class="md-nav__title" for="__nav_3">
+          <span class="md-nav__icon md-icon"></span>
+          Machine Building
+        </label>
+        <ul class="md-nav__list" data-md-scrollfix>
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Machine_Building/machine-building/" class="md-nav__link">
+        Final Project
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Machine_Building/sample-project/" class="md-nav__link">
+        Another project
+      </a>
+    </li>
+  
+
+            
+          
+        </ul>
+      </nav>
+    </li>
+  
+
+    
+      
+      
+      
+
+  
+  
+  
+    
+    <li class="md-nav__item md-nav__item--nested">
+      
+      
+        <input class="md-nav__toggle md-toggle" data-md-toggle="__nav_4" type="checkbox" id="__nav_4" >
+      
+      
+      
+      
+        <label class="md-nav__link" for="__nav_4">
+          Weekly Group Assignments
+          <span class="md-nav__icon md-icon"></span>
+        </label>
+      
+      <nav class="md-nav" aria-label="Weekly Group Assignments" data-md-level="1">
+        <label class="md-nav__title" for="__nav_4">
+          <span class="md-nav__icon md-icon"></span>
+          Weekly Group Assignments
+        </label>
+        <ul class="md-nav__list" data-md-scrollfix>
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week01/" class="md-nav__link">
+        1. Principles and practices / Project management
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week02/" class="md-nav__link">
+        2. Computer Aided design
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week03/" class="md-nav__link">
+        3. Computer controlled cutting
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week04/" class="md-nav__link">
+        4. Electronics production
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week05/" class="md-nav__link">
+        5. 3D Scanning and printing
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week06/" class="md-nav__link">
+        6. Electronics design
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week07/" class="md-nav__link">
+        7. Computer controlled machining
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week08/" class="md-nav__link">
+        8. Embedded programming
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week09/" class="md-nav__link">
+        9. Molding and casting
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week10/" class="md-nav__link">
+        10. Output devices
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week11/" class="md-nav__link">
+        11. Mechanical design / Machine design
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week12/" class="md-nav__link">
+        12. Input devices
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week13/" class="md-nav__link">
+        13. Networking and communications
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week14/" class="md-nav__link">
+        14. Interface and application programming
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week15/" class="md-nav__link">
+        15. Wildcard week
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week16/" class="md-nav__link">
+        16. Applications and implications
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week17/" class="md-nav__link">
+        17. Invention, intellectual property and income
+      </a>
+    </li>
+  
+
+            
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/Weekly_Group_Assignments/week18/" class="md-nav__link">
+        18. Project development
+      </a>
+    </li>
+  
+
+            
+          
+        </ul>
+      </nav>
+    </li>
+  
+
+    
+      
+      
+      
+
+  
+  
+  
+    
+    <li class="md-nav__item md-nav__item--nested">
+      
+      
+        <input class="md-nav__toggle md-toggle" data-md-toggle="__nav_5" type="checkbox" id="__nav_5" >
+      
+      
+      
+      
+        <label class="md-nav__link" for="__nav_5">
+          About my lab
+          <span class="md-nav__icon md-icon"></span>
+        </label>
+      
+      <nav class="md-nav" aria-label="About my lab" data-md-level="1">
+        <label class="md-nav__title" for="__nav_5">
+          <span class="md-nav__icon md-icon"></span>
+          About my lab
+        </label>
+        <ul class="md-nav__list" data-md-scrollfix>
+          
+            
+              
+  
+  
+  
+    <li class="md-nav__item">
+      <a href="/2022/labs/kannai/about_my_lab/" class="md-nav__link">
+        About Fab Lab Kannai
+      </a>
+    </li>
+  
+
+            
+          
+        </ul>
+      </nav>
+    </li>
+  
+
+    
+  </ul>
+</nav>
+                  </div>
+                </div>
+              </div>
+            
+            
+          
+          <div class="md-content" data-md-component="content">
+            <article class="md-content__inner md-typeset">
+              
+  <h1>404 - Not found</h1>
+
+            </article>
+          </div>
+        </div>
+        
+      </main>
+      
+        <footer class="md-footer">
+  
+  <div class="md-footer-meta md-typeset">
+    <div class="md-footer-meta__inner md-grid">
+      <div class="md-copyright">
+  
+    <div class="md-copyright__highlight">
+      Copyright 2022 Your name - Creative Commons Attribution Non Commercial
+    </div>
+  
+  
+    Made with
+    <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
+      Material for MkDocs
+    </a>
+  
+</div>
+      
+        <div class="md-social">
+  
+    
+    
+      
+      
+    
+    <a href="https://instagram.com/fabacademany" target="_blank" rel="noopener" title="instagram.com" class="md-social__link">
+      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"/></svg>
+    </a>
+  
+    
+    
+      
+      
+    
+    <a href="https://facebook.com/fabacademany" target="_blank" rel="noopener" title="facebook.com" class="md-social__link">
+      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M504 256C504 119 393 8 256 8S8 119 8 256c0 123.78 90.69 226.38 209.25 245V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.28c-30.8 0-40.41 19.12-40.41 38.73V256h68.78l-11 71.69h-57.78V501C413.31 482.38 504 379.78 504 256z"/></svg>
+    </a>
+  
+    
+    
+      
+      
+    
+    <a href="https://twitter.com/fabacademany" target="_blank" rel="noopener" title="twitter.com" class="md-social__link">
+      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/></svg>
+    </a>
+  
+    
+    
+      
+      
+    
+    <a href="https://linkedin.com/in/academany" target="_blank" rel="noopener" title="linkedin.com" class="md-social__link">
+      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/></svg>
+    </a>
+  
+</div>
+      
+    </div>
+  </div>
+</footer>
+      
+    </div>
+    <div class="md-dialog" data-md-component="dialog">
+      <div class="md-dialog__inner md-typeset"></div>
+    </div>
+    <script id="__config" type="application/json">{"base": "/2022/labs/kannai/", "features": ["navigation.tabs", "navigation.instant"], "translations": {"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing", "select.version.title": "Select version"}, "search": "/2022/labs/kannai/assets/javascripts/workers/search.092fa1f6.min.js"}</script>
+    
+    
+      <script src="/2022/labs/kannai/assets/javascripts/bundle.5a9542cf.min.js"></script>
+      
+    
+  </body>
+</html>var prog = JSON.parse(JSON.stringify({"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"407.58350726962306","left":"3676.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1024.583507269623","left":"4134.994408259125","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"894.5835072696229","left":"3703.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.3135579179893032":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         offset()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"507.58350726962306","left":"4135.994408259125","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"540.583507269623","left":"3164.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.2892270043957246":{"definition":"//\n// view toolpath\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view toolpath'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.toolpath.event()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'toolpath',cmd)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n\t   scene = new THREE.Scene()\n\t   mod.scene = scene\n\t   var width = win.innerWidth\n\t   var height = win.innerHeight\n\t   var aspect = width/height\n\t   var near = 0.1\n\t   var far = 1000000\n\t   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n\t   mod.camera = camera\n\t   scene.add(camera)\n\t   //\n\t   // add renderer\n\t   //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n\t   renderer.setSize(width,height)\n\t   container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t      camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n\t      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n\t      renderer.render(scene,camera)\n\t      }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n\t   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n\t   renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1244.583507269623","left":"2494.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.9557599338778935":{"definition":"//\n// mill raster 2D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2016\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = 0.0156\n   mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value)\n   mod.cut_in.value = 0.004\n   mod.cut_mm.value = 25.4*parseFloat(mod.cut_in.value)\n   mod.max_in.value = 0.004\n   mod.max_mm.value = 25.4*parseFloat(mod.max_in.value)\n   mod.number.value = 4\n   mod.stepover.value = 0.5\n   mod.merge.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'array',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value)) && (evt.detail.length > 0)) {\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event()\n               }\n            else {\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               merge_path()\n               clear_path()\n               draw_path(mod.path)\n               draw_connections()\n               add_depth()\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'object',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   diameter:{type:'number',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'number',\n      event:function(){\n         var pixels = mod.offset*parseFloat(mod.dia_in.value)*mod.dpi\n         mods.output(mod,'offset',pixels)\n         }\n      },\n   toolpath:{type:'object',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = mod.depth\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         mod.offset = 0.5\n         mod.offsetCount = 0\n         mod.path = []\n         clear_path()\n         outputs.diameter.event()\n         outputs.offset.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_path\n//\nfunction clear_path() {\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_path\n//\nfunction merge_path() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if ((mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])\n         && (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0])) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"465.58350726962306","left":"2517.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.10309904694903338":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 1) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if (((vecpath.length > 1) && (sort == false)) || (vecpath.length == 1))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1010.5835072696229","left":"3208.994408259124","filename":"undefined","inputs":{},"outputs":{}},"0.992472539363189":{"definition":"//\n// set object\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'set PCB defaults'\n//\n// initialization\n//\nvar init = function() {\n   //\n   add_output('mill traces (1/64)')\n   add_variable('tool diameter (in)','var00')\n   mod.var00.value = '0.0156'\n   add_variable('cut depth (in)','var01')\n   mod.var01.value = '0.004'\n   add_variable('max depth (in)','var02')\n   mod.var02.value = '0.004'\n   add_variable('offset number','var03')\n   mod.var03.value = '4'\n   //\n   add_output('mill outline (1/32)')\n   add_variable('tool diameter (in)','var10')\n   mod.var10.value = '0.027559055118110236'\n   add_variable('cut depth (in)','var11')\n   mod.var11.value = '0.027559055118110236'\n   add_variable('max depth (in)','var12')\n   mod.var12.value = '0.07086614173228346'\n   add_variable('offset number','var13')\n   mod.var13.value = '1'\n   //\n   }\n//\n// inputs\n//\nvar inputs = {}\n//\n// outputs\n//\nvar outputs = {\n   settings:{type:'',\n      event:function(vars){\n         mods.output(mod,'settings',vars)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   }\n//\n// local functions\n//\nfunction add_output(label) {\n   if (mod.settings == undefined) {\n      mod.settings = {}\n      }\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n      var text = document.createTextNode(label)\n         span.appendChild(text)\n      btn.appendChild(span)\n      var f = function(label) {\n         btn.addEventListener('click',function() {\n            for (var s in mod.settings)\n               mod.settings[s].span.style.fontWeight = 'normal'\n            mod.settings[label].span.style.fontWeight = 'bold'\n            var vars = {}\n            for (var v in mod.settings[label].variables)\n               vars[v] = mod.settings[label].variables[v].value\n            outputs.settings.event(vars)\n            })\n         }(label)\n      mod.settings[label] = {span:span,variables:{}}\n      mod.div.appendChild(btn)\n      mod.setting = label\n   mod.div.appendChild(document.createElement('br'))\n   }\nfunction add_variable(label,variable) {\n   var text = document.createTextNode(label)\n      mod.div.appendChild(text)\n   mod.div.appendChild(document.createTextNode(': '))\n   input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      mod[variable] = input\n      mod.div.appendChild(input)\n   mod.settings[mod.setting].variables[label] = input \n   mod.div.appendChild(document.createElement('br'))\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"385.58350726962306","left":"1973.9944082591242","filename":"undefined","inputs":{},"outputs":{}},"0.8622681794886853":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1232.9473065265367","left":"2013.9337776993061","filename":"modules/file/save","inputs":{},"outputs":{}},"0.057765477464730264":{"definition":"//\n// read SVG\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read SVG'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt) {\n         svg_load_handler({target:{result:evt.detail}})\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   SVG:{type:'string',\n      event:function(){\n         mods.output(mod,'SVG',mod.str)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         svg_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select SVG file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('file:')\n         info.appendChild(text)\n         mod.name = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('width:')\n         info.appendChild(text)\n         mod.width = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('height:')\n         info.appendChild(text)\n         mod.height = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('units per inch:')\n         info.appendChild(text)\n         mod.units = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction svg_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = svg_load_handler\n   var input_file = mod.file.files[0]\n   var file_name = input_file.name\n   mod.name.nodeValue = \"file: \"+file_name\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction svg_load_handler(event) {\n   mod.str = event.target.result\n   //\n   // parse size\n   //\n   var i = mod.str.indexOf(\"width\")\n   if (i == -1) {\n      mod.width.nodeValue = \"width: not found\"\n      mod.height.nodeValue = \"height: not found\"\n      }\n   else {\n      var i1 = mod.str.indexOf(\"\\\"\",i+1)\n      var i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var width = mod.str.substring(i1+1,i2)\n      i = mod.str.indexOf(\"height\")\n      i1 = mod.str.indexOf(\"\\\"\",i+1)\n      i2 = mod.str.indexOf(\"\\\"\",i1+1)\n      var height = mod.str.substring(i1+1,i2)\n      ih = mod.str.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      mod.width.nodeValue = \"width: \"+width\n      mod.height.nodeValue = \"height: \"+height\n      mod.units.nodeValue = \"units per inch: \"+units\n      }\n   //\n   // display\n   //\n   var img = new Image()\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.str)\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      outputs.SVG.event()\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"349.03488793999406","left":"1166.572396970906","filename":"modules/read/svg","inputs":{},"outputs":{}},"0.5022735462618486":{"definition":"//\n// convert SVG image\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'convert SVG image'\n//\n// initialization\n//\nvar init = function() {\n   mod.dpi.value = '1000'\n   }\n//\n// inputs\n//\nvar inputs = {\n   SVG:{type:'string',\n      event:function(evt){\n         mod.svg = evt.detail\n         get_size()\n         load_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = \"SVG image\"\n         obj.dpi = parseFloat(mod.dpi.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   //\n   // invert button\n   //\n   div.appendChild(document.createTextNode(' '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   //\n   // dpi\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('dpi: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.dpi = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // units\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('units: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         load_image()\n         })\n      div.appendChild(input)\n      mod.unitstext = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   //\n   // fill\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('fill background: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.checked = true\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill= input\n   //\n   // size\n   //\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('image size:')\n      div.appendChild(text)\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(pixels)')\n      div.appendChild(text)\n      mod.pixels = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(inches)')\n      div.appendChild(text)\n      mod.inches = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mm = text\n   }\n//\n// local functions\n//\n// get size\n//\nfunction get_size() {\n   var i = mod.svg.indexOf(\"width\")\n   if (i == -1) {\n      var width = 1\n      var height = 1\n      var units = 90\n      }\n   else {\n      var i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      var i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var width = mod.svg.substring(i1+1,i2)\n      i = mod.svg.indexOf(\"height\")\n      i1 = mod.svg.indexOf(\"\\\"\",i+1)\n      i2 = mod.svg.indexOf(\"\\\"\",i1+1)\n      var height = mod.svg.substring(i1+1,i2)\n      ih = mod.svg.indexOf(\"height\")\n      if (width.indexOf(\"px\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 90\n         }\n      else if (width.indexOf(\"mm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 25.4\n         }\n      else if (width.indexOf(\"cm\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 2.54\n         }\n      else if (width.indexOf(\"in\") != -1) {\n         width = width.slice(0,-2)\n         height = height.slice(0,-2)\n         var units = 1\n         }\n      else {\n         var units = 90\n         }\n      }\n   mod.width = parseFloat(width)\n   mod.height = parseFloat(height)\n   mod.units = units\n   mod.unitstext.value = units\n   }\n//\n// load image\n//\nfunction load_image() {\n   var src = \"data:image/svg+xml;base64,\"+window.btoa(mod.svg)\n   var img = new Image()\n   img.setAttribute(\"src\",src)\n   img.onload = function() {\n      var dpi = parseFloat(mod.dpi.value)\n      var units = parseFloat(mod.unitstext.value)\n      var width = parseInt(dpi*mod.width/units)\n      var height = parseInt(dpi*mod.height/units)\n      mod.pixels.nodeValue =\n         width+' x '+height+\" (pixels)\"\n      mod.inches.nodeValue =\n         (width/dpi).toFixed(3)+' x '+(height/dpi).toFixed(3)+\" (inches)\"\n      mod.mm.nodeValue =\n      (25.4*width/dpi).toFixed(3)+' x '+(25.4*height/dpi).toFixed(3)+\" (mm)\"\n      mod.img.width = width\n      mod.img.height = height\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.clearRect(0,0,width,height)\n      ctx.drawImage(img,0,0,width,height)\n      if (mod.fill.checked)\n         fill_image()\n      else\n         output_image()\n      }\n   }\n//\n// output_image\n//\nfunction output_image() {\n   if (mod.img.width > mod.img.height) {\n      var x0 = 0\n      var y0 = mod.canvas.height*.5*(1-mod.img.height/mod.img.width)\n      var w = mod.canvas.width\n      var h = mod.canvas.width*mod.img.height/mod.img.width\n      }\n   else {\n      var x0 = mod.canvas.width*.5*(1-mod.img.width/mod.img.height)\n      var y0 = 0\n      var w = mod.canvas.height*mod.img.width/mod.img.height\n      var h = mod.canvas.height\n      }\n   var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,w,h)\n   outputs.image.event()\n   outputs.imageInfo.event()\n   }\n//\n// fill image\n//\nfunction fill_image() {\n   var blob = new Blob(['('+fill_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      output_image()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction fill_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            alpha = buf[(h-1-row)*w*4+col*4+3]/255\n            buf[(h-1-row)*w*4+col*4+0] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = (1-alpha)*255+alpha*buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+invert_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction invert_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"455.69493521948414","left":"1540.852199242571","filename":"modules/convert/svg/image","inputs":{},"outputs":{}},"0.9604471250218796":{"definition":"//\n// path to G-code\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// Updated: Steven Chew\n// Date:    Feb 20 2019\n// Comments: Added option to output in inch or mm\n// Date:... Oct 28 2019\n// Comments: Corrected feedrate conversion\n//           - inch/s to inch/min\n//...........- mm/s to mm/min\n//\n// Updated: Neil Gershenfeld\n// Date: Oct 28 2020\n// Comments: added mm/s vs mm/min option\n//\n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to G-code'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '2.5'\n   mod.plungespeed.value = '2.5'\n   mod.jogheight.value = '2'\n   mod.spindlespeed.value = '11000'\n   mod.tool.value = '1'\n   mod.coolantoff.checked = true\n   mod.formatMm.checked = true\n   mod.unitMinutes.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".nc\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // tool\n   //\n   div.appendChild(document.createTextNode('tool: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.tool = input\n   div.appendChild(document.createElement('br'))\n   //\n   // coolant\n   //\n   div.appendChild(document.createTextNode('coolant:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolanton'\n      div.appendChild(input)\n      mod.coolanton = input\n   div.appendChild(document.createTextNode('on'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'coolant'\n      input.id = mod.div.id+'coolantoff'\n      div.appendChild(input)\n      mod.coolantoff = input\n   div.appendChild(document.createTextNode('off'))\n   div.appendChild(document.createElement('br'))\n   //\n   // inch or mm format\n   //\n   div.appendChild(document.createTextNode('format:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatInch'\n      input.checked = true\n      div.appendChild(input)\n      mod.formatInch = input\n   div.appendChild(document.createTextNode('inch'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'formatMm'\n      div.appendChild(input)\n      mod.formatMm = input\n   div.appendChild(document.createTextNode('mm'))\n   div.appendChild(document.createElement('br'))\n   //\n   // second or minute rate units \n   //\n   div.appendChild(document.createTextNode('rate units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitSeconds'\n      input.checked = true\n      div.appendChild(input)\n      mod.unitSeconds = input\n   div.appendChild(document.createTextNode('second'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitMinutes'\n      div.appendChild(input)\n      mod.unitMinutes = input\n   div.appendChild(document.createTextNode('minute'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   var dx = 25.4*mod.width/mod.dpi\n   var cut_speed = parseFloat(mod.cutspeed.value)\n   var plunge_speed = parseFloat(mod.plungespeed.value)\n   var jog_height = parseFloat(mod.jogheight.value)\n   var nx = mod.width\n   var scale = dx/(nx-1)\n   var in_mm_scale = 1\n   if (mod.formatInch.checked) {\n      dx /= 25.4\n      scale /= 25.4\n      cut_speed /= 25.4\n      plunge_speed /= 25.4\n      jog_height /= 25.4\n      }\n   if (mod.unitMinutes.checked) {\n      cut_speed *= 60\n      plunge_speed *= 60\n      }\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var tool = parseInt(mod.tool.value)\n   str = \"%\\n\" // tape start\n   str += \"G17\\n\" // xy plane\n   if (mod.formatInch.checked)\n      str += \"G20\\n\" // inches\n   if (mod.formatMm.checked)\n      str += \"G21\\n\" // mm\n   str += \"G40\\n\" // cancel tool radius compensation\n   str += \"G49\\n\" // cancel tool length compensation\n   str += \"G54\\n\" // coordinate system 1\n   str += \"G80\\n\" // cancel canned cycles\n   str += \"G90\\n\" // absolute coordinates\n   str += \"G94\\n\" // feed/minute units\n   str += \"T\"+tool+\"M06\\n\" // tool selection, tool change\n   str += \"F\"+cut_speed.toFixed(4)+\"\\n\" // feed rate\n   str += \"S\"+spindle_speed+\"\\n\" // spindle speed\n   if (mod.coolanton.checked)\n      str += \"M08\\n\" // coolant on\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before starting spindle\n   str += \"M03\\n\" // spindle on clockwise\n //str += \"G04 P1000\\n\" // give spindle 1 second to spin up................comment out\n   //str += \"G04 P1\\n\" // give spindle 1 Millisecond to spin up..............comment out\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\"\n      str += \"G00X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+jog_height.toFixed(4)+\"\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"G01Z\"+z.toFixed(4)+\" F\"+plunge_speed.toFixed(4)+\"\\n\"\n      str += \"F\"+cut_speed.toFixed(4)+\"\\n\" //restore xy feed rate\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"G01X\"+x.toFixed(4)+\"Y\"+y.toFixed(4)+\"Z\"+z.toFixed(4)+\"\\n\"\n         }\n      }\n   //\n   // finish\n   //\n   str += \"G00Z\"+jog_height.toFixed(4)+\"\\n\" // move up before stopping spindle\n   str += \"M05\\n\" // spindle stop\n   if (mod.coolanton.checked)\n      str += \"M09\\n\" // coolant off\n   str += \"M30\\n\" // program end and reset\n   str += \"%\\n\" // tape end\n   //\n   // output file\n   //\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"781.1683166499946","left":"1985.839479173399","filename":"modules/path/formats/g-code","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3135579179893032\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"toolpath\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10309904694903338\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.992472539363189\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"settings\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9557599338778935\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.057765477464730264\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5022735462618486\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"SVG\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2892270043957246\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.9604471250218796\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8622681794886853\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}))
 window.mods_prog_load(prog)
 </script>
 </body>
-- 
GitLab