diff --git a/files.html b/files.html index 5ad8a3b048afb9d1cf35ef041773b8122519cfd9..ca834d3da28269201dc723d41517845013e7054f 100644 --- a/files.html +++ b/files.html @@ -175,6 +175,7 @@ <i> raster</i><br> <a href='./modules/processes/mill/raster/2.5D'>2.5D</a><br> <a href='./modules/processes/mill/raster/2D'>2D</a><br> + <a href='./modules/processes/mill/raster/3D'>3D</a><br> <i> read</i><br> <a href='./modules/read/png'>png</a><br> <a href='./modules/read/stl'>stl</a><br> diff --git a/modules/index.js b/modules/index.js index 323490210b37c54952aecf3c71f84e4a6c914a4e..1a4683d2f0f65040cc236950edcdecc9e740fb60 100644 --- a/modules/index.js +++ b/modules/index.js @@ -144,6 +144,7 @@ module_label(' mill') module_label(' raster') module_menu(' 2.5D','modules/processes/mill/raster/2.5D') module_menu(' 2D','modules/processes/mill/raster/2D') +module_menu(' 3D','modules/processes/mill/raster/3D') module_label('read') module_menu(' png','modules/read/png') module_menu(' stl','modules/read/stl') diff --git a/modules/processes/mill/raster/3D b/modules/processes/mill/raster/3D new file mode 100644 index 0000000000000000000000000000000000000000..59551cc2226281e6cb025864d28f3bca85388a23 --- /dev/null +++ b/modules/processes/mill/raster/3D @@ -0,0 +1,450 @@ +// +// mill raster 3D +// +// Neil Gershenfeld 1/18/20 +// +// This work may be reproduced, modified, distributed, performed, and +// displayed for any purpose, but must acknowledge the mods +// project. Copyright is retained and must be preserved. The work is +// provided as is; no warranty is provided, and users accept all +// liability. +// +// closure +// +(function(){ +// +// module globals +// +var mod = {} +// +// name +// +var name = 'mill raster 3D' +// +// initialization +// +var init = function() { + mod.dia_in.value = 0.0156 + mod.dia_mm.value = 25.4*parseFloat(mod.dia_in.value) + mod.stepover.value = 0.5 + } +// +// inputs +// +var inputs = { + map:{type:'',label:'height map', + event:function(evt){ + mod.width = evt.detail.width + mod.height = evt.detail.height + var ctx = mod.img.getContext("2d") + ctx.canvas.width = mod.width + ctx.canvas.height = mod.height + console.log(mod.width) + console.log(mod.height) + }}} +// +// outputs +// +var outputs = { + toolpath:{type:'', + event:function(){ + cmd = {} + cmd.path = mod.path + cmd.name = mod.name + cmd.dpi = mod.dpi + cmd.width = mod.width + cmd.height = mod.height + cmd.depth = mod.depth + mods.output(mod,'toolpath',cmd) + }}} +// +// interface +// +var interface = function(div){ + mod.div = div + // + // tool diameter + // + div.appendChild(document.createTextNode('tool diameter')) + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode('mm: ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + input.addEventListener('input',function(){ + mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4 + }) + div.appendChild(input) + mod.dia_mm = input + div.appendChild(document.createTextNode(' in: ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + input.addEventListener('input',function(){ + mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4 + }) + div.appendChild(input) + mod.dia_in = input + div.appendChild(document.createElement('br')) + // + // stepover + // + div.appendChild(document.createTextNode('stepover (0-1): ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + div.appendChild(input) + mod.stepover = input + div.appendChild(document.createElement('br')) + // + // tool shape + // + div.appendChild(document.createTextNode('tool shape: ')) + div.appendChild(document.createTextNode('flat end')) + var input = document.createElement('input') + input.type = 'radio' + input.name = mod.div.id+'shape' + input.id = mod.div.id+'flatend' + input.checked = true + div.appendChild(input) + mod.flatend= input + div.appendChild(document.createElement('br')) + // + // direction + // + div.appendChild(document.createTextNode('direction: ')) + div.appendChild(document.createTextNode('x')) + var input = document.createElement('input') + input.type = 'radio' + input.name = mod.div.id+'direction' + input.id = mod.div.id+'dirx' + input.checked = true + div.appendChild(input) + mod.dirx = input + div.appendChild(document.createElement('br')) + // + // calculate + // + var btn = document.createElement('button') + btn.style.padding = mods.ui.padding + btn.style.margin = 1 + var span = document.createElement('span') + var text = document.createTextNode('calculate') + mod.label = text + span.appendChild(text) + mod.labelspan = span + btn.appendChild(span) + btn.addEventListener('click',function(){ + mod.label.nodeValue = 'calculating' + mod.labelspan.style.fontWeight = 'bold' + mod.offset = 0.5 + mod.offsetCount = 0 + mod.path = [] + clear_path() + outputs.diameter.event() + outputs.offset.event() + }) + div.appendChild(btn) + div.appendChild(document.createTextNode(' ')) + // + // view + // + var btn = document.createElement('button') + btn.style.padding = mods.ui.padding + btn.style.margin = 1 + btn.appendChild(document.createTextNode('view')) + btn.addEventListener('click',function(){ + var win = window.open('') + var btn = document.createElement('button') + btn.appendChild(document.createTextNode('close')) + btn.style.padding = mods.ui.padding + btn.style.margin = 1 + btn.addEventListener('click',function(){ + win.close() + }) + win.document.body.appendChild(btn) + win.document.body.appendChild(document.createElement('br')) + var svg = document.getElementById(mod.div.id+'svg') + var clone = svg.cloneNode(true) + clone.setAttribute('width',mod.img.width) + clone.setAttribute('height',mod.img.height) + win.document.body.appendChild(clone) + }) + div.appendChild(btn) + div.appendChild(document.createElement('br')) + // + // on-screen SVG + // + var svgNS = "http://www.w3.org/2000/svg" + var svg = document.createElementNS(svgNS,"svg") + svg.setAttribute('id',mod.div.id+'svg') + svg.setAttributeNS("http://www.w3.org/2000/xmlns/", + "xmlns:xlink","http://www.w3.org/1999/xlink") + svg.setAttribute('width',mods.ui.canvas) + svg.setAttribute('height',mods.ui.canvas) + svg.style.backgroundColor = 'rgb(255,255,255)' + var g = document.createElementNS(svgNS,'g') + g.setAttribute('id',mod.div.id+'g') + svg.appendChild(g) + div.appendChild(svg) + div.appendChild(document.createElement('br')) + // + // off-screen image canvas + // + var canvas = document.createElement('canvas') + mod.img = canvas + } +// +// local functions +// +// set_values +// +function set_values(settings) { + for (var s in settings) { + switch(s) { + case 'tool diameter (in)': + mod.dia_in.value = settings[s] + mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4 + break + case 'cut depth (in)': + mod.cut_in.value = settings[s] + mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4 + break + case 'max depth (in)': + mod.max_in.value = settings[s] + mod.max_mm.value = parseFloat(mod.max_in.value)*25.4 + break + case 'offset number': + mod.number.value = settings[s] + break + } + } + } +// +// clear_path +// +function clear_path() { + var svg = document.getElementById(mod.div.id+'svg') + svg.setAttribute('viewBox',"0 0 "+(mod.img.width-1)+" "+(mod.img.height-1)) + var g = document.getElementById(mod.div.id+'g') + svg.removeChild(g) + var g = document.createElementNS('http://www.w3.org/2000/svg','g') + g.setAttribute('id',mod.div.id+'g') + svg.appendChild(g) + } +// +// accumulate_path +// todo: replace inefficient insertion sort +// todo: move sort out of main thread +// +function accumulate_path(path) { + var forward = mod.forward.checked + var conventional = mod.conventional.checked + var sort = mod.sort.checked + for (var segnew = 0; segnew < path.length; ++segnew) { + if (conventional) + path[segnew].reverse() + if (mod.path.length == 0) + mod.path.splice(0,0,path[segnew]) + else if (sort) { + var xnew = path[segnew][0][0] + var ynew = path[segnew][0][1] + var dmin = Number.MAX_VALUE + var segmin = -1 + for (var segold = 0; segold < mod.path.length; ++segold) { + var xold = mod.path[segold][0][0] + var yold = mod.path[segold][0][1] + var dx = xnew-xold + var dy = ynew-yold + var d = Math.sqrt(dx*dx+dy*dy) + if (d < dmin) { + dmin = d + segmin = segold + } + } + if (forward) + mod.path.splice(segmin+1,0,path[segnew]) + else + mod.path.splice(segmin,0,path[segnew]) + } + else { + if (forward) + mod.path.splice(mod.path.length,0,path[segnew]) + else + mod.path.splice(0,0,path[segnew]) + } + } + } +// +// merge_path +// +function merge_path() { + var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value) + var seg = 0 + while (seg < (mod.path.length-1)) { + var xold = mod.path[seg][mod.path[seg].length-1][0] + var yold = mod.path[seg][mod.path[seg].length-1][1] + var xnew = mod.path[seg+1][0][0] + var ynew = mod.path[seg+1][0][1] + var dx = xnew-xold + var dy = ynew-yold + var d = Math.sqrt(dx*dx+dy*dy) + if (d < dmerge) + mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1])) + else + seg += 1 + } + } +// +// add_depth +// +function add_depth() { + var cut = parseFloat(mod.cut_in.value) + var max = parseFloat(mod.max_in.value) + var newpath = [] + for (var seg = 0; seg < mod.path.length; ++seg) { + var depth = cut + if (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0]) { + var newseg = [] + while (depth <= max) { + var idepth = -Math.round(mod.dpi*depth) + for (var pt = 0; pt < mod.path[seg].length; ++pt) { + var point = mod.path[seg][pt].concat(idepth) + newseg.splice(newseg.length,0,point) + } + if (depth == max) + break + depth += cut + if (depth > max) + depth = max + } + newpath.splice(newpath.length,0,newseg) + } + else { + var newseg = [] + while (depth <= max) { + var idepth = -Math.round(mod.dpi*depth) + for (var pt = 0; pt < mod.path[seg].length; ++pt) { + var point = mod.path[seg][pt].concat(idepth) + newseg.splice(newseg.length,0,point) + } + newpath.splice(newpath.length,0,newseg) + newseg = [] + if (depth == max) + break + depth += cut + if (depth > max) + depth = max + } + } + } + mod.path = newpath + mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi) + } +// +// draw_path +// +function draw_path(path) { + var g = document.getElementById(mod.div.id+'g') + var h = mod.img.height + var w = mod.img.width + var xend = null + var yend = null + // + // loop over segments + // + for (var segment = 0; segment < path.length; ++segment) { + if (path[segment].length > 1) { + // + // loop over points + // + for (var point = 1; point < path[segment].length; ++point) { + var line = document.createElementNS('http://www.w3.org/2000/svg','line') + line.setAttribute('stroke','black') + line.setAttribute('stroke-width',1) + line.setAttribute('stroke-linecap','round') + var x1 = path[segment][point-1][0] + var y1 = h-path[segment][point-1][1]-1 + var x2 = path[segment][point][0] + var y2 = h-path[segment][point][1]-1 + xend = x2 + yend = y2 + line.setAttribute('x1',x1) + line.setAttribute('y1',y1) + line.setAttribute('x2',x2) + line.setAttribute('y2',y2) + var dx = x2-x1 + var dy = y2-y1 + var d = Math.sqrt(dx*dx+dy*dy) + if (d > 0) { + nx = 6*dx/d + ny = 6*dy/d + var tx = 3*dy/d + var ty = -3*dx/d + g.appendChild(line) + triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon') + triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty) + +' '+(x2-nx-tx)+','+(y2-ny-ty)) + triangle.setAttribute('fill','black') + g.appendChild(triangle) + } + } + } + } + } +// +// draw_connections +// +function draw_connections() { + var g = document.getElementById(mod.div.id+'g') + var h = mod.img.height + var w = mod.img.width + // + // loop over segments + // + for (var segment = 1; segment < mod.path.length; ++segment) { + // + // draw connection from previous segment + // + var line = document.createElementNS('http://www.w3.org/2000/svg','line') + line.setAttribute('stroke','red') + line.setAttribute('stroke-width',1) + line.setAttribute('stroke-linecap','round') + var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0] + var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1 + var x2 = mod.path[segment][0][0] + var y2 = h-mod.path[segment][0][1]-1 + line.setAttribute('x1',x1) + line.setAttribute('y1',y1) + line.setAttribute('x2',x2) + line.setAttribute('y2',y2) + var dx = x2-x1 + var dy = y2-y1 + var d = Math.sqrt(dx*dx+dy*dy) + if (d > 0) { + nx = 6*dx/d + ny = 6*dy/d + var tx = 3*dy/d + var ty = -3*dx/d + g.appendChild(line) + triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon') + triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty) + +' '+(x2-nx-tx)+','+(y2-ny-ty)) + triangle.setAttribute('fill','red') + g.appendChild(triangle) + } + } + } +// +// return values +// +return ({ + mod:mod, + name:name, + init:init, + inputs:inputs, + outputs:outputs, + interface:interface + }) +}()) +