/******************************************************************************\ This file is part of the Buildbotics firmware. Copyright (c) 2015 - 2018, Buildbotics LLC All rights reserved. This file ("the software") is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2 as published by the Free Software Foundation. You should have received a copy of the GNU General Public License, version 2 along with the software. If not, see . The software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the software. If not, see . For information regarding this software email: "Joseph Coffland" \******************************************************************************/ 'use strict' var api = require('./api'); var cookie = require('./cookie')('bbctrl-'); function _is_array(x) { return Object.prototype.toString.call(x) === '[object Array]'; } function escapeHTML(s) { var entityMap = {'&': '&', '<': '<', '>': '>'}; return String(s).replace(/[&<>]/g, function (s) {return entityMap[s];}); } module.exports = { template: '#control-view-template', props: ['config', 'template', 'state'], data: function () { return { mach_units: 'METRIC', mdi: '', last_file: undefined, last_file_time: undefined, toolpath: {}, toolpath_progress: 0, axes: 'xyzabc', history: [], speed_override: 1, feed_override: 1, manual_home: {x: false, y: false, z: false, a: false, b: false, c: false}, position_msg: {x: false, y: false, z: false, a: false, b: false, c: false}, axis_position: 0, jog_step: cookie.get_bool('jog-step'), jog_adjust: parseInt(cookie.get('jog-adjust', 2)), deleteGCode: false, tab: 'auto', jog_incr: 1.0, tool_msg: false, tool_diameter: 6.35, toolpath_msg: {x: false, y: false, z: false, a: false, b: false, c: false}, ask_home: true, ask_home_msg: false, ask_zero_xy_msg: false, ask_zero_z_msg: false, showGcodeMessage: false } }, components: { 'axis-control': require('./axis-control'), 'path-viewer': require('./path-viewer'), 'gcode-viewer': require('./gcode-viewer') }, watch: { 'state.imperial': { handler: function (imperial) { this.mach_units = imperial ? 'IMPERIAL' : 'METRIC'; }, immediate: true }, 'state.bitDiameter': { handler: function (bitDiameter) { console.log("New bitDiameter " + bitDiameter); console.log("Units: " + this.mach_units); if(this.mach_units == 'IMPERIAL') this.tool_diameter = bitDiameter / 25.4; else this.tool_diameter = bitDiameter; console.log("Tool diameter: " + this.tool_diameter); }, immediate: true }, mach_units: function (units) { if ((units == 'METRIC') != this.metric) this.send(units == 'METRIC' ? 'G21' : 'G20'); this.units_changed(); }, 'state.line': function () { if (this.mach_state != 'HOMING') this.$broadcast('gcode-line', this.state.line); }, 'state.selected_time': function () {this.load()}, jog_step: function () {cookie.set_bool('jog-step', this.jog_step)}, jog_adjust: function () {cookie.set('jog-adjust', this.jog_adjust)} }, computed: { metric: function () {return !this.state.imperial}, mach_state: function () { var cycle = this.state.cycle; var state = this.state.xx; if (typeof cycle != 'undefined' && state != 'ESTOPPED' && (cycle == 'jogging' || cycle == 'homing')) return cycle.toUpperCase(); return state || '' }, pause_reason: function () {return this.state.pr}, is_running: function () { return this.mach_state == 'RUNNING' || this.mach_state == 'HOMING'; }, is_stopping: function () {return this.mach_state == 'STOPPING'}, is_holding: function () {return this.mach_state == 'HOLDING'}, is_ready: function () {return this.mach_state == 'READY'}, is_idle: function () {return this.state.cycle == 'idle'}, is_paused: function () { return this.is_holding && (this.pause_reason == 'User pause' || this.pause_reason == 'Program pause') }, can_mdi: function () {return this.is_idle || this.state.cycle == 'mdi'}, can_set_axis: function () { return this.is_idle // TODO allow setting axis position during pause return this.is_idle || this.is_paused }, message: function () { if (this.mach_state == 'ESTOPPED') return this.state.er; if (this.mach_state == 'HOLDING') return this.state.pr; if (this.state.messages.length) return this.state.messages.slice(-1)[0].text; return ''; }, highlight_state: function () { return this.mach_state == 'ESTOPPED' || this.mach_state == 'HOLDING'; }, plan_time: function () {return this.state.plan_time}, plan_time_remaining: function () { if (!(this.is_stopping || this.is_running || this.is_holding)) return 0; return this.toolpath.time - this.plan_time }, eta: function () { if (this.mach_state != 'RUNNING') return ''; var remaining = this.plan_time_remaining; var d = new Date(); d.setSeconds(d.getSeconds() + remaining); return d.toLocaleString(); }, progress: function () { if (!this.toolpath.time || this.is_ready) return 0; var p = this.plan_time / this.toolpath.time; return p < 1 ? p : 1; } }, events: { jog: function (axis, power) { var data = {ts: new Date().getTime()}; data[axis] = power; api.put('jog', data); }, back2zero: function(axis0,axis1) { this.send("G0"+axis0+"0"+axis1+"0"); }, step: function (axis, value) { this.send('M70\nG91\nG0' + axis + value + '\nM72'); } }, ready: function () {this.load()}, methods: { units_changed : function() { console.log("Units changed!"); if(this.mach_units == 'METRIC') { document.getElementById("jog_button_fine").innerHTML = "0.1"; document.getElementById("jog_button_small").innerHTML = "1.0"; document.getElementById("jog_button_medium").innerHTML = "10"; document.getElementById("jog_button_large").innerHTML = "100"; this.tool_diameter = this.tool_diameter * 25.4; this.tool_diameter = this.tool_diameter.toFixed(3); } else { document.getElementById("jog_button_fine").innerHTML = "0.005"; document.getElementById("jog_button_small").innerHTML = "0.05"; document.getElementById("jog_button_medium").innerHTML = "0.5"; document.getElementById("jog_button_large").innerHTML = "5"; this.tool_diameter = this.tool_diameter / 25.4; this.tool_diameter = this.tool_diameter.toFixed(3); } this.set_jog_incr('small'); }, set_tool_diameter : function (new_diameter) { if(isNaN(new_diameter)) return; this.tool_msg = false; this.tool_diameter = parseFloat(new_diameter); this.probe_xyz(); }, set_jog_incr: function(newValue) { //this.jog_incr = newValue; document.getElementById("jog_button_fine").style.fontWeight = 'normal'; document.getElementById("jog_button_small").style.fontWeight = 'normal'; document.getElementById("jog_button_medium").style.fontWeight = 'normal'; document.getElementById("jog_button_large").style.fontWeight = 'normal'; if(newValue == 'fine') { document.getElementById("jog_button_fine").style.fontWeight = 'bold'; if(this.mach_units == 'METRIC') this.jog_incr = 0.1; else this.jog_incr = 0.005; } else if(newValue == 'small') { document.getElementById("jog_button_small").style.fontWeight = 'bold'; if(this.mach_units == 'METRIC') this.jog_incr = 1.0; else this.jog_incr = 0.05; } else if(newValue == 'medium') { document.getElementById("jog_button_medium").style.fontWeight = 'bold'; if(this.mach_units == 'METRIC') this.jog_incr = 10; else this.jog_incr = 0.5; } else if(newValue == 'large') { document.getElementById("jog_button_large").style.fontWeight = 'bold'; if(this.mach_units == 'METRIC') this.jog_incr = 100; else this.jog_incr = 5; } }, goto_zero(zero_x,zero_y,zero_z,zero_a) { var xcmd = ""; var ycmd = ""; var zcmd = ""; var acmd = ""; if(zero_x) xcmd = "X0"; if(zero_y) ycmd = "Y0"; if(zero_z) zcmd = "Z0"; if(zero_a) acmd = "A0"; this.ask_zero_xy_msg = false; this.ask_zero_z_msg = false; this.send('G90\nG0' + xcmd + ycmd + zcmd + acmd + '\n'); }, probe_xyz() { let xoffset = this.config.probe["probe-xdim"]; let yoffset = this.config.probe["probe-ydim"]; let zoffset = this.config.probe["probe-zdim"]; let fastSeek = this.config.probe["probe-fast-seek"]; let slowSeek = this.config.probe["probe-slow-seek"]; xoffset += this.tool_diameter / 2.0; yoffset += this.tool_diameter / 2.0; if (this.mach_units !== "METRIC") { xoffset /= 25.4; yoffset /= 25.4; zoffset /= 25.4; slowSeek /= 25.4; fastSeek /= 25.4; } const zlift = 1; // After probing Z, we want to drop the bit down: // Ideally, 12.7mm/0.5in // And we don't want to be more than 75% down on the probe block let plunge = Math.min(12.7, zoffset * 0.75); plunge += zlift; // Compensate for the fact that we lift after probing Z xoffset = xoffset.toFixed(5); yoffset = yoffset.toFixed(5); zoffset = zoffset.toFixed(5); slowSeek = slowSeek.toFixed(5); fastSeek = fastSeek.toFixed(5); plunge = plunge.toFixed(5); slowSeek = `F${slowSeek}`; fastSeek = `F${fastSeek}`; this.send(` G21 G92 X0 Y0 Z0 G38.2 Z -25.4 ${fastSeek} G91 G1 Z 1 G38.2 Z -2 ${slowSeek} G92 Z ${zoffset} G91 G0 Z ${zlift} G91 G0 X 20 G91 G0 Z -${plunge} G38.2 X -20 ${fastSeek} G91 G1 X 1 G38.2 X -2 ${slowSeek} G92 X ${xoffset} G91 G0 X 1 G91 G0 Y 20 G91 G0 X -20 G38.2 Y -20 ${fastSeek} G91 G1 Y 1 G38.2 Y -2 ${slowSeek} G92 Y ${yoffset} G91 G0 Y 3 G91 G0 Z 25.4 G90 G0 X0 Y0 M2 `); }, probe_z() { let fastSeek = this.config.probe["probe-fast-seek"]; let slowSeek = this.config.probe["probe-slow-seek"]; let zoffset = this.config.probe["probe-zdim"]; if (this.mach_units !== "METRIC") { zoffset /= 25.4; slowSeek /= 25.4; fastSeek /= 25.4; } zoffset = zoffset.toFixed(5); slowSeek = slowSeek.toFixed(5); fastSeek = fastSeek.toFixed(5); slowSeek = `F${slowSeek}`; fastSeek = `F${fastSeek}`; this.send(` G21 G92 Z0 G38.2 Z -25.4 ${fastSeek} G91 G1 Z 1 G38.2 Z -2 ${slowSeek} G92 Z ${zoffset} G91 G0 Z3 M2 `); }, jog_fn: function (x_jog,y_jog,z_jog,a_jog) { var xcmd = "X" + x_jog * this.jog_incr; var ycmd = "Y" + y_jog * this.jog_incr; var zcmd = "Z" + z_jog * this.jog_incr; var acmd = "A" + a_jog * this.jog_incr; console.log("Jog command: " + this.jog_incr); //debugger; this.send('G91\nG0' + xcmd + ycmd + zcmd + acmd + '\n'); }, send: function (msg) {this.$dispatch('send', msg)}, load: function () { var file_time = this.state.selected_time; var file = this.state.selected; if (this.last_file == file && this.last_file_time == file_time) return; this.last_file = file; this.last_file_time = file_time; this.$broadcast('gcode-load', file); this.$broadcast('gcode-line', this.state.line); this.toolpath_progress = 0; this.load_toolpath(file, file_time); }, load_toolpath: function (file, file_time) { this.toolpath = {}; if (!file) return; api.get('path/' + file).done(function (toolpath) { if (this.last_file_time != file_time) return; if (typeof toolpath.progress == 'undefined') { toolpath.filename = file; this.toolpath_progress = 1; this.showGcodeMessage = false; this.toolpath = toolpath; var state = this.$root.state; var bounds = toolpath.bounds; for (var axis of 'xyzabc') { Vue.set(state, 'path_min_' + axis, bounds.min[axis]); Vue.set(state, 'path_max_' + axis, bounds.max[axis]); } } else { this.showGcodeMessage = true; this.toolpath_progress = toolpath.progress; this.load_toolpath(file, file_time); // Try again } }.bind(this)); }, submit_mdi: function () { this.send(this.mdi); if (!this.history.length || this.history[0] != this.mdi) this.history.unshift(this.mdi); this.mdi = ''; }, mdi_start_pause: function () { if (this.state.xx == 'RUNNING') this.pause(); else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING') this.unpause(); else this.submit_mdi(); }, load_history: function (index) {this.mdi = this.history[index];}, open: function (e) { // If we don't reset the form the browser may cache file if name is same // even if contents have changed $('.gcode-file-input')[0].reset(); $('.gcode-file-input input').click(); }, upload: function (e) { var files = e.target.files || e.dataTransfer.files; if (!files.length) return; var file = files[0]; var fd = new FormData(); fd.append('gcode', file); api.upload('file', fd) .done(function () { this.last_file_time = undefined; // Force reload this.$broadcast('gcode-reload', file.name); }.bind(this)).fail(function (error) { api.alert('Upload failed', error) }.bind(this)); }, delete_current: function () { if (this.state.selected) api.delete('file/' + this.state.selected); this.deleteGCode = false; }, delete_all: function () { api.delete('file'); this.deleteGCode = false; }, home: function (axis) { this.ask_home = false; this.ask_home_msg = false; if (typeof axis == 'undefined') api.put('home'); else { if (this[axis].homingMode != 'manual') api.put('home/' + axis); else this.manual_home[axis] = true; } }, set_home: function (axis, position) { this.manual_home[axis] = false; api.put('home/' + axis + '/set', {position: parseFloat(position)}); }, unhome: function (axis) { this.position_msg[axis] = false; api.put('home/' + axis + '/clear'); }, show_set_position: function (axis) { this.axis_position = 0; this.position_msg[axis] = true; }, show_toolpath_msg : function(axis) { this.toolpath_msg[axis] = true; }, set_position: function (axis, position) { this.position_msg[axis] = false; api.put('position/' + axis, {'position': parseFloat(position)}); }, zero_all: function () { for (var axis of 'xyzabc') if (this[axis].enabled) this.zero(axis); }, zero: function (axis) { if (typeof axis == 'undefined') this.zero_all(); else this.set_position(axis, 0); }, start_pause: function () { if (this.state.xx == 'RUNNING') this.pause(); else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING') this.unpause(); else this.start(); }, start: function () {api.put('start')}, pause: function () {api.put('pause')}, unpause: function () {api.put('unpause')}, optional_pause: function () {api.put('pause/optional')}, stop: function () {api.put('stop')}, step: function () {api.put('step')}, override_feed: function () {api.put('override/feed/' + this.feed_override)}, override_speed: function () { api.put('override/speed/' + this.speed_override) }, current: function (axis, value) { var x = value / 32.0; if (this.state[axis + 'pl'] == x) return; var data = {}; data[axis + 'pl'] = x; this.send(JSON.stringify(data)); } }, mixins: [require('./axis-vars')] }