diff --git a/modules/index.js b/modules/index.js index cd0ffdc3ef792ff35895ca909b16e69e3017dc89..cdc41472f56741051ef0af68caf4ec21b663a326 100644 --- a/modules/index.js +++ b/modules/index.js @@ -1,3 +1,5 @@ +module_menu(' hpgl','modules/read/hpgl.js') +module_menu(' PPA','modules/path/machines/CBA/PPA.js') module_label('character') module_menu(' convert','modules/character/convert') module_menu(' in out','modules/character/in%20out') @@ -127,6 +129,8 @@ module_menu(' dxf','modules/path/formats/dxf') module_menu(' g-code','modules/path/formats/g-code') module_menu(' svg','modules/path/formats/svg') module_label(' machines') +module_label(' CBA') +module_menu(' PPA','modules/path/machines/CBA/PPA.js') module_label(' Roland') module_label(' milling') module_menu(' MDX-20','modules/path/machines/Roland/milling/MDX-20') @@ -151,6 +155,7 @@ module_menu(' png','modules/read/png') module_menu(' stl','modules/read/stl') module_menu(' svg','modules/read/svg') module_menu(' text','modules/read/text') +module_menu(' hpgl','modules/read/hpgl.js') module_label('socket') module_label(' server') module_menu(' device','modules/socket/server/device') @@ -170,4 +175,3 @@ module_menu(' color','modules/ui/color') module_menu(' label','modules/ui/label') module_menu(' slider','modules/ui/slider') module_menu(' text window','modules/ui/text%20window') - diff --git a/modules/path/machines/CBA/PPA.js b/modules/path/machines/CBA/PPA.js new file mode 100644 index 0000000000000000000000000000000000000000..afe47047bdfffa15fd8ec4da02209744251a7f6c --- /dev/null +++ b/modules/path/machines/CBA/PPA.js @@ -0,0 +1,317 @@ +// +// Parallel Prismatic Actuator (PPA) is a 3 axis motion stage for on site setup. +// +// David Preiss / Eyal Perry +// (c) Massachusetts Institute of Technology 2021 +// +// 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 = 'PPA' +// +// initialization +// +var init = function() { + mod.baseLength.value = 39.5*25.4; + mod.maxHypo.value = 1208.2; + mod.yMargin.value = 5; + mod.chunkLength.value = 0.1; + draw_boundary(); + } +// +// inputs +// +var inputs = { + toolpath:{type:'', + event:function(evt){ + console.log(evt.detail); + mod.path = evt.detail.path; + mod.maxX = evt.detail.maxX; + mod.maxY = evt.detail.maxY; + make_path() + }}} +// +// outputs +// +var outputs = { + file:{type:'', + event:function(str){ + obj = {} + obj.type = 'file' + obj.name = mod.name+'.camm' + obj.contents = str + mods.output(mod,'file',obj) + }}} +// +// interface +// +var interface = function(div){ + mod.div = div + // + // on-screen drawing canvas + // + var canvas = document.createElement('canvas') + canvas.width = mods.ui.canvas + canvas.height = mods.ui.canvas + canvas.style.backgroundColor = 'rgb(255,255,255)' + div.appendChild(canvas) + mod.canvas = canvas + div.appendChild(document.createElement('br')) + + div.appendChild(document.createTextNode('base length (mm): ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + input.addEventListener('change', (event) => { + draw_boundary(); + }); + div.appendChild(input) + mod.baseLength = input + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode('max hypotenuse (mm): ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + input.addEventListener('change', (event) => { + draw_boundary(); + }); + div.appendChild(input) + mod.maxHypo = input + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode('y margin (%): ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + input.addEventListener('change', (event) => { + draw_boundary(); + }); + div.appendChild(input) + mod.yMargin = input + div.appendChild(document.createElement('br')) + div.appendChild(document.createTextNode('chunk length (mm): ')) + var input = document.createElement('input') + input.type = 'text' + input.size = 6 + div.appendChild(input) + mod.chunkLength = input + div.appendChild(document.createElement('br')) + + +} +// +// local functions +// +function draw_boundary() { + var w = mod.canvas.width; + var h = mod.canvas.height; + + var bl = parseFloat(mod.baseLength.value); + var mh = parseFloat(mod.maxHypo.value); + var margin = parseFloat(mod.yMargin.value) / 100; + var triH = Math.sqrt(Math.pow(mh, 2) - Math.pow(bl /2, 2)); + var maxW = bl; + if (bl < mh) { + maxW = mh + 2 * (mh - bl); + } + var centerW = maxW / 2; + console.log("bl:", bl); + console.log("mh:", mh); + console.log("triH:", triH); + console.log("maxW:", maxW); + + var scaleX = w / maxW; + var scaleY = h / triH; + mod.scale = scaleX > scaleY ? scaleY : scaleX; + mod.drawOffsetX = (centerW - (bl/2))*mod.scale; + + var angle = Math.asin(triH / mh); + console.log("angle", angle * 180 / Math.PI); + + var ctx = mod.canvas.getContext('2d'); + ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height) + + ctx.beginPath(); + ctx.setLineDash([5, 10]); + ctx.arc((centerW - (bl/2))*mod.scale, 0, mh * mod.scale, 0, angle); + ctx.strokeStyle = "green"; + ctx.stroke(); + + ctx.beginPath(); + ctx.setLineDash([5, 10]); + ctx.arc((centerW + (bl/2))*mod.scale, 0, mh * mod.scale, Math.PI - angle, Math.PI); + ctx.strokeStyle = "green"; + ctx.stroke(); + + ctx.beginPath(); + ctx.setLineDash([5, 10]); + ctx.moveTo(0, triH * margin * mod.scale); + ctx.lineTo(w, triH * margin * mod.scale); + ctx.strokeStyle = "green"; + ctx.stroke(); +} + +function flip_path() { + for (var i = 0; i < mod.path.length; i++) { + var tmp = mod.path[i][0]; + mod.path[i][0] = mod.path[i][1]; + mod.path[i][1] = tmp; + } + var tmp = mod.maxY; + mod.maxY = mod.maxX; + mod.maxX = tmp; +} + +function scale_path() { + var bl = parseFloat(mod.baseLength.value); + // machine scale + var machineScale = bl / mod.maxX; + + for (var i = 0; i < mod.path.length; i++) { + mod.path[i][0] *= machineScale; + mod.path[i][1] *= machineScale; + } + + mod.maxX *= machineScale; + mod.maxY *= machineScale; +} + +function center_path() { + var bl = parseFloat(mod.baseLength.value); + var mh = parseFloat(mod.maxHypo.value); + var margin = parseFloat(mod.yMargin.value) / 100; + var triH = Math.sqrt(Math.pow(mh, 2) - Math.pow(bl /2, 2)); + + var offsetX = (bl / 2) - (mod.maxX / 2); + var offsetY = mh * margin; + + console.log("offsetX", offsetX); + console.log("offsetY", offsetY); + + for (var i = 0; i < mod.path.length; i++) { + mod.path[i][0] += offsetX; + mod.path[i][1] += offsetY; + } + + mod.maxX += offsetX; + mod.maxY += offsetY; +} + +function make_path() { + draw_boundary(); + + if (!mod.path) return; + + if (mod.maxY > mod.maxX) { + flip_path(); + } + + scale_path(); + + center_path(); + + + + // draw + var ctx = mod.canvas.getContext('2d'); + + var path = mod.path; + + ctx.beginPath(); + ctx.setLineDash([]) + ctx.moveTo(path[0][0] * mod.scale + mod.drawOffsetX, path[0][1] * mod.scale); + for (var i = 1; i < path.length; i++) { + if (path[i][2] == 0) { + ctx.lineTo(path[i][0] * mod.scale + mod.drawOffsetX, path[i][1] * mod.scale); + } else { + ctx.moveTo(path[i][0] * mod.scale + mod.drawOffsetX, path[i][1] * mod.scale); + } + } + ctx.strokeStyle = "red"; + ctx.stroke(); + + + // construct path + var str = ""; + var bl = parseFloat(mod.baseLength.value); + var cl = parseFloat(mod.chunkLength.value); + var mh = parseFloat(mod.maxHypo.value); + + // init + var l1 = mh; + var l2 = mh; + var z = 1; + + str += "L1,"+l1.toFixed(4).toString()+",L2,"+l2.toFixed(4).toString()+",Z,"+z.toString()+"\n"; + + var x = bl / 2; + var y = Math.sqrt(Math.pow(mh, 2) - Math.pow(x, 2)); + + console.log("Start x,y", x, y); + + ctx.beginPath(); + ctx.setLineDash([2, 3]); + ctx.moveTo(x * mod.scale + mod.drawOffsetX, y * mod.scale); + for (var i = 0; i < mod.path.length; i++) { + var new_x = mod.path[i][0]; + var new_y = mod.path[i][1]; + var new_z = mod.path[i][2]; + + var d = Math.sqrt(Math.pow(new_x - x, 2) + Math.pow(new_y - y, 2)); + + var dir_x = new_x - x; + var dir_y = new_y - y; + var dir_norm = Math.sqrt(Math.pow(dir_x, 2) + Math.pow(dir_y, 2)); + if (dir_norm > 0) { + dir_x /= dir_norm; + dir_y /= dir_norm; + while (d > cl) { + x += dir_x * cl * Math.sqrt(2) / 2; + y += dir_y * cl * Math.sqrt(2) / 2; + d = Math.sqrt(Math.pow(new_x - x, 2) + Math.pow(new_y - y, 2)); + + l1 = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + l2 = Math.sqrt(Math.pow(bl - x, 2) + Math.pow(y, 2)); + str += "L1,"+l1.toFixed(4).toString()+",L2,"+l2.toFixed(4).toString()+",Z,"+z.toString()+"\n"; + + ctx.lineTo(x * mod.scale + mod.drawOffsetX, y * mod.scale); + } + } + + x = new_x; + y = new_y; + z = new_z; + l1 = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + l2 = Math.sqrt(Math.pow(bl - x, 2) + Math.pow(y, 2)); + str += "L1,"+l1.toFixed(4).toString()+",L2,"+l2.toFixed(4).toString()+",Z,"+z.toString()+"\n"; + ctx.lineTo(x * mod.scale + mod.drawOffsetX, y * mod.scale); + } + console.log(str); + ctx.strokeStyle = "blue"; + ctx.stroke(); + //outputs.file.event(str) +} + +// +// return values +// +return ({ + name:name, + init:init, + inputs:inputs, + outputs:outputs, + interface:interface + }) +}()) diff --git a/modules/read/hpgl.js b/modules/read/hpgl.js new file mode 100644 index 0000000000000000000000000000000000000000..72544c780c7ff6b64b0b3b41805b613a14e97262 --- /dev/null +++ b/modules/read/hpgl.js @@ -0,0 +1,248 @@ +// +// read SVG +// +// Eyal Perry +// (c) Massachusetts Institute of Technology 2021 +// +// 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 = 'read HPGL' +// +// initialization +// +var init = function() { + } +// +// inputs +// +var inputs = { + HPGL:{type:'string', + event:function(evt) { + hpgl_load_handler({target:{result:evt.detail}}) + }} +} +// +// outputs +// +var outputs = { + toolpath:{type:'array', + event:function(){ + cmd = {} + cmd.path = mod.path; + cmd.maxX = mod.maxX; + cmd.maxY = mod.maxY; + mods.output(mod,'toolpath',cmd) + }} +} +// +// interface +// +var interface = function(div){ + mod.div = div + // + // file input control + // + var file = document.createElement('input') + file.setAttribute('type','file') + file.setAttribute('id',div.id+'file_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() { + hpgl_read_handler() + }) + div.appendChild(file) + mod.file = file + // + // on-screen drawing canvas + // + var canvas = document.createElement('canvas') + canvas.width = mods.ui.canvas + canvas.height = mods.ui.canvas + canvas.style.backgroundColor = 'rgb(255,255,255)' + div.appendChild(canvas) + mod.canvas = canvas + div.appendChild(document.createElement('br')) + // + // off-screen image canvas + // + var canvas = document.createElement('canvas') + mod.img = canvas + // + // file select button + // + var btn = document.createElement('button') + btn.style.padding = mods.ui.padding + btn.style.margin = 1 + btn.appendChild(document.createTextNode('select HPGL file')) + btn.addEventListener('click',function(){ + var file = document.getElementById(div.id+'file_input') + file.value = null + file.click() + }) + div.appendChild(btn) + div.appendChild(document.createElement('br')) + // + // view button + // + var btn = document.createElement('button') + btn.style.padding = mods.ui.padding + btn.style.margin = 1 + btn.appendChild(document.createTextNode('view')) + btn.addEventListener('click',function(){ + var win = window.open('') + var btn = document.createElement('button') + btn.appendChild(document.createTextNode('close')) + btn.style.padding = mods.ui.padding + btn.style.margin = 1 + btn.addEventListener('click',function(){ + win.close() + }) + win.document.body.appendChild(btn) + win.document.body.appendChild(document.createElement('br')) + var canvas = document.createElement('canvas') + canvas.width = mod.img.width + canvas.height = mod.img.height + win.document.body.appendChild(canvas) + var ctx = canvas.getContext("2d") + ctx.drawImage(mod.img,0,0) + }) + div.appendChild(btn) + div.appendChild(document.createElement('br')) + // + // info div + // + var info = document.createElement('div') + info.setAttribute('id',div.id+'info') + var text = document.createTextNode('file:') + info.appendChild(text) + mod.name = text + info.appendChild(document.createElement('br')) + var text = document.createTextNode('width:') + info.appendChild(text) + mod.width = text + info.appendChild(document.createElement('br')) + var text = document.createTextNode('height:') + info.appendChild(text) + mod.height = text + div.appendChild(info) + } +// +// local functions +// +// read handler +// +function hpgl_read_handler(event) { + // + // read as text + // + var file_reader = new FileReader() + file_reader.onload = hpgl_load_handler + var input_file = mod.file.files[0] + var file_name = input_file.name + mod.name.nodeValue = "file: "+file_name + file_reader.readAsText(input_file) + } +// +// load handler +// +function hpgl_load_handler(event) { + var cmds = event.target.result.split(";"); + var path = []; + for (var i = 0; i < cmds.length; i++) { + var cmd = cmds[i]; + if (cmd.startsWith("IN")) { + // initialize, start a plotting job + } else if (cmd.startsWith("SP")) { + // select pen + console.log("Select pen:", cmd.substring(2)); + } else if (cmd.startsWith("PU")) { + // pen up + console.log("Pen up", cmd.substring(2)); + var xy = cmd.substring(2).split(","); + path.push([parseInt(xy[0]), parseInt(xy[1]), 1]); + } else if (cmd.startsWith("PD")) { + // pen down + console.log("Pen down:", cmd.substring(2)); + var pathRaw = cmd.substring(2).split(","); + for (var j = 0; j < pathRaw.length; j += 2) { + path.push([parseInt(pathRaw[j]), parseInt(pathRaw[j+1]), 0]) + } + } + } + // + // parse size + // + var minX = path[0][0]; + var maxX = path[0][0]; + var minY = path[0][1]; + var maxY = path[0][1]; + for (var i = 1; i < path.length; i++) { + if (path[i][0] < minX) minX = path[i][0]; + if (path[i][0] > maxX) maxX = path[i][0]; + if (path[i][1] < minY) minY = path[i][1]; + if (path[i][1] > maxY) maxY = path[i][1]; + } + mod.width.nodeValue = "width: "+minX.toString() + " - " + maxX.toString(); + mod.height.nodeValue = "height: "+minY.toString() + " - " + maxY.toString(); + mod.maxX = maxX; + mod.maxY = maxY; + var x0 = minX; + var width = maxX-minX; + var y0 = minY; + var height = maxY-minY; + // + // display + // + var scaleX = mod.canvas.width / width; + var scaleY = mod.canvas.height / height; + var scale = scaleX > scaleY ? scaleY : scaleX; + + var ctx = mod.canvas.getContext('2d'); + ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height) + + ctx.beginPath(); + ctx.moveTo((path[0][0] - x0) * scale, (path[0][1] - y0) * scale); + for (var i = 1; i < path.length; i++) { + if (path[i][2] == 0) { + ctx.lineTo((path[i][0] - x0) * scale, (path[i][1] - y0) * scale); + } else { + ctx.moveTo((path[i][0] - x0) * scale, (path[i][1] - y0) * scale); + } + } + ctx.strokeStyle = "red"; + ctx.stroke(); + + mod.path = path; + outputs.toolpath.event(); + +} +// +// return values +// +return ({ + mod:mod, + name:name, + init:init, + inputs:inputs, + outputs:outputs, + interface:interface + }) +}())