Verison 1.0.3 Release
Based on Buildbotics 0.4.14
This commit is contained in:
138
src/js/admin-general-view.js
Normal file
138
src/js/admin-general-view.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
var api = require('./api');
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#admin-general-view-template',
|
||||
props: ['config', 'state'],
|
||||
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
configRestored: false,
|
||||
confirmReset: false,
|
||||
configReset: false,
|
||||
latest: '',
|
||||
autoCheckUpgrade: true
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
latest_version: function (version) {this.latest = version}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {
|
||||
this.autoCheckUpgrade = this.config.admin['auto-check-upgrade']
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
backup: function () {
|
||||
document.getElementById('download-target').src = '/api/config/download';
|
||||
},
|
||||
|
||||
|
||||
restore_config: function () {
|
||||
// If we don't reset the form the browser may cache file if name is same
|
||||
// even if contents have changed
|
||||
$('.restore-config')[0].reset();
|
||||
$('.restore-config input').click();
|
||||
},
|
||||
|
||||
|
||||
restore: function (e) {
|
||||
var files = e.target.files || e.dataTransfer.files;
|
||||
if (!files.length) return;
|
||||
|
||||
var fr = new FileReader();
|
||||
fr.onload = function (e) {
|
||||
var config;
|
||||
|
||||
try {
|
||||
config = JSON.parse(e.target.result);
|
||||
} catch (ex) {
|
||||
api.alert("Invalid config file");
|
||||
return;
|
||||
}
|
||||
|
||||
api.put('config/save', config).done(function (data) {
|
||||
this.$dispatch('update');
|
||||
this.configRestored = true;
|
||||
|
||||
}.bind(this)).fail(function (error) {
|
||||
api.alert('Restore failed', error);
|
||||
})
|
||||
}.bind(this);
|
||||
|
||||
fr.readAsText(files[0]);
|
||||
},
|
||||
|
||||
|
||||
reset: function () {
|
||||
this.confirmReset = false;
|
||||
api.put('config/reset').done(function () {
|
||||
this.$dispatch('update');
|
||||
this.configReset = true;
|
||||
|
||||
}.bind(this)).fail(function (error) {
|
||||
api.alert('Reset failed', error);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
check: function () {this.$dispatch('check')},
|
||||
upgrade: function () {this.$dispatch('upgrade')},
|
||||
|
||||
|
||||
upload_firmware: function () {
|
||||
// If we don't reset the form the browser may cache file if name is same
|
||||
// even if contents have changed
|
||||
$('.upload-firmware')[0].reset();
|
||||
$('.upload-firmware input').click();
|
||||
},
|
||||
|
||||
|
||||
upload: function (e) {
|
||||
var files = e.target.files || e.dataTransfer.files;
|
||||
if (!files.length) return;
|
||||
this.$dispatch('upload', files[0]);
|
||||
},
|
||||
|
||||
|
||||
change_auto_check_upgrade: function () {
|
||||
this.config.admin['auto-check-upgrade'] = this.autoCheckUpgrade;
|
||||
this.$dispatch('config-changed');
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/js/admin-network-view.js
Normal file
177
src/js/admin-network-view.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
var api = require('./api');
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#admin-network-view-template',
|
||||
props: ['config', 'state'],
|
||||
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
hostnameSet: false,
|
||||
usernameSet: false,
|
||||
passwordSet: false,
|
||||
redirectTimeout: 0,
|
||||
hostname: '',
|
||||
username: '',
|
||||
current: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
wifi_mode: 'client',
|
||||
wifi_ssid: '',
|
||||
wifi_ch: undefined,
|
||||
wifi_pass: '',
|
||||
wifiConfirm: false,
|
||||
rebooting: false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {
|
||||
api.get('hostname').done(function (hostname) {
|
||||
this.hostname = hostname;
|
||||
}.bind(this));
|
||||
|
||||
api.get('remote/username').done(function (username) {
|
||||
this.username = username;
|
||||
}.bind(this));
|
||||
|
||||
api.get('wifi').done(function (config) {
|
||||
this.wifi_mode = config.mode;
|
||||
this.wifi_ssid = config.ssid;
|
||||
this.wifi_ch = config.channel;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
redirect: function (hostname) {
|
||||
if (0 < this.redirectTimeout) {
|
||||
this.redirectTimeout -= 1;
|
||||
setTimeout(function () {this.redirect(hostname)}.bind(this), 1000);
|
||||
|
||||
} else location.hostname = hostname;
|
||||
},
|
||||
|
||||
|
||||
set_hostname: function () {
|
||||
api.put('hostname', {hostname: this.hostname}).done(function () {
|
||||
this.redirectTimeout = 45;
|
||||
this.hostnameSet = true;
|
||||
|
||||
api.put('reboot').always(function () {
|
||||
if (String(location.hostname) == 'localhost') return;
|
||||
|
||||
var hostname = this.hostname;
|
||||
if (String(location.hostname).endsWith('.local'))
|
||||
hostname += '.local'
|
||||
this.$dispatch('hostname-changed', hostname);
|
||||
this.redirect(hostname);
|
||||
}.bind(this));
|
||||
|
||||
}.bind(this)).fail(function (error) {
|
||||
api.alert('Set hostname failed', error);
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
set_username: function () {
|
||||
api.put('remote/username', {username: this.username}).done(function () {
|
||||
this.usernameSet = true;
|
||||
}.bind(this)).fail(function (error) {
|
||||
api.alert('Set username failed', error);
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
set_password: function () {
|
||||
if (this.password != this.password2) {
|
||||
alert('Passwords to not match');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.password.length < 6) {
|
||||
alert('Password too short');
|
||||
return;
|
||||
}
|
||||
|
||||
api.put('remote/password', {
|
||||
current: this.current,
|
||||
password: this.password
|
||||
}).done(function () {
|
||||
this.passwordSet = true;
|
||||
}.bind(this)).fail(function (error) {
|
||||
api.alert('Set password failed', error);
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
config_wifi: function () {
|
||||
this.wifiConfirm = false;
|
||||
|
||||
if (!this.wifi_ssid.length) {
|
||||
alert('SSID not set');
|
||||
return;
|
||||
}
|
||||
|
||||
if (32 < this.wifi_ssid.length) {
|
||||
alert('SSID longer than 32 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.wifi_pass.length && this.wifi_pass.length < 8) {
|
||||
alert('WiFi password shorter than 8 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
if (128 < this.wifi_pass.length) {
|
||||
alert('WiFi password longer than 128 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
this.rebooting = true;
|
||||
|
||||
var config = {
|
||||
mode: this.wifi_mode,
|
||||
channel: this.wifi_ch,
|
||||
ssid: this.wifi_ssid,
|
||||
pass: this.wifi_pass
|
||||
}
|
||||
|
||||
api.put('wifi', config).fail(function (error) {
|
||||
api.alert('Failed to configure WiFi', error);
|
||||
this.rebooting = false;
|
||||
}.bind(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/js/api.js
Normal file
104
src/js/api.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
function api_cb(method, url, data, config) {
|
||||
config = $.extend({
|
||||
type: method,
|
||||
url: '/api/' + url,
|
||||
dataType: 'json',
|
||||
cache: false
|
||||
}, config);
|
||||
|
||||
if (typeof data == 'object') {
|
||||
config.data = JSON.stringify(data);
|
||||
config.contentType = 'application/json; charset=utf-8';
|
||||
}
|
||||
|
||||
var d = $.Deferred();
|
||||
|
||||
$.ajax(config).success(function (data, status, xhr) {
|
||||
d.resolve(data, status, xhr);
|
||||
|
||||
}).error(function (xhr, status, error) {
|
||||
var text = xhr.responseText;
|
||||
try {text = $.parseJSON(xhr.responseText)} catch(e) {}
|
||||
if (!text) text = error;
|
||||
|
||||
d.reject(text, xhr, status, error);
|
||||
console.debug('API Error: ' + url + ': ' + text);
|
||||
});
|
||||
|
||||
return d.promise();
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
get: function (url, config) {
|
||||
return api_cb('GET', url, undefined, config);
|
||||
},
|
||||
|
||||
|
||||
put: function(url, data, config) {
|
||||
return api_cb('PUT', url, data, config);
|
||||
},
|
||||
|
||||
|
||||
post: function(url, data, config) {
|
||||
return api_cb('POST', url, data, config);
|
||||
},
|
||||
|
||||
|
||||
upload: function(url, data, config) {
|
||||
config = $.extend({
|
||||
processData: false,
|
||||
contentType: false,
|
||||
cache: false,
|
||||
data: data
|
||||
}, config);
|
||||
|
||||
return api_cb('PUT', url, undefined, config);
|
||||
},
|
||||
|
||||
|
||||
'delete': function (url, config) {
|
||||
return api_cb('DELETE', url, undefined, config);
|
||||
},
|
||||
|
||||
|
||||
alert: function (msg, error) {
|
||||
if (typeof error != 'undefined') {
|
||||
if (typeof error.message != 'undefined')
|
||||
msg += '\n' + error.message;
|
||||
else msg += '\n' + JSON.stringify(error);
|
||||
}
|
||||
|
||||
alert(msg);
|
||||
}
|
||||
}
|
||||
431
src/js/app.js
Normal file
431
src/js/app.js
Normal file
@@ -0,0 +1,431 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
var api = require('./api');
|
||||
var cookie = require('./cookie')('bbctrl-');
|
||||
var Sock = require('./sock');
|
||||
const { exec } = require('child_process');
|
||||
|
||||
function compare_versions(a, b) {
|
||||
var reStripTrailingZeros = /(\.0+)+$/;
|
||||
var segsA = a.replace(reStripTrailingZeros, '').split('.');
|
||||
var segsB = b.replace(reStripTrailingZeros, '').split('.');
|
||||
var l = Math.min(segsA.length, segsB.length);
|
||||
|
||||
for (var i = 0; i < l; i++) {
|
||||
var diff = parseInt(segsA[i], 10) - parseInt(segsB[i], 10);
|
||||
if (diff) return diff;
|
||||
}
|
||||
|
||||
return segsA.length - segsB.length;
|
||||
}
|
||||
|
||||
|
||||
function is_object(o) {return o !== null && typeof o == 'object'}
|
||||
function is_array(o) {return Array.isArray(o)}
|
||||
|
||||
|
||||
function update_array(dst, src) {
|
||||
while (dst.length) dst.pop()
|
||||
for (var i = 0; i < src.length; i++)
|
||||
Vue.set(dst, i, src[i]);
|
||||
}
|
||||
|
||||
|
||||
function update_object(dst, src, remove) {
|
||||
var props, index, key, value;
|
||||
|
||||
if (remove) {
|
||||
props = Object.getOwnPropertyNames(dst);
|
||||
|
||||
for (index in props) {
|
||||
key = props[index];
|
||||
if (!src.hasOwnProperty(key))
|
||||
Vue.delete(dst, key);
|
||||
}
|
||||
}
|
||||
|
||||
props = Object.getOwnPropertyNames(src);
|
||||
for (index in props) {
|
||||
key = props[index];
|
||||
value = src[key];
|
||||
|
||||
if (is_array(value) && dst.hasOwnProperty(key) && is_array(dst[key]))
|
||||
update_array(dst[key], value);
|
||||
|
||||
else if (is_object(value) && dst.hasOwnProperty(key) && is_object(dst[key]))
|
||||
update_object(dst[key], value, remove);
|
||||
|
||||
else Vue.set(dst, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Vue({
|
||||
el: 'body',
|
||||
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
status: 'connecting',
|
||||
currentView: 'loading',
|
||||
index: -1,
|
||||
modified: false,
|
||||
template: require('../resources/config-template.json'),
|
||||
config: {
|
||||
settings: {units: 'METRIC'},
|
||||
motors: [{}, {}, {}, {}],
|
||||
version: '<loading>'
|
||||
},
|
||||
state: {messages: []},
|
||||
video_size: cookie.get('video-size', 'small'),
|
||||
crosshair: cookie.get('crosshair', 'false') != 'false',
|
||||
errorTimeout: 30,
|
||||
errorTimeoutStart: 0,
|
||||
errorShow: false,
|
||||
errorMessage: '',
|
||||
confirmUpgrade: false,
|
||||
confirmUpload: false,
|
||||
firmwareUpgrading: false,
|
||||
checkedUpgrade: false,
|
||||
firmwareName: '',
|
||||
latestVersion: '',
|
||||
password: '',
|
||||
ipAddress: '0.0.0.0',
|
||||
wifiSSID: ''
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
components: {
|
||||
'estop': {template: '#estop-template'},
|
||||
'loading-view': {template: '<h1>Loading...</h1>'},
|
||||
'control-view': require('./control-view'),
|
||||
'settings-view': require('./settings-view'),
|
||||
'motor-view': require('./motor-view'),
|
||||
'tool-view': require('./tool-view'),
|
||||
'io-view': require('./io-view'),
|
||||
'admin-general-view': require('./admin-general-view'),
|
||||
'admin-network-view': require('./admin-network-view'),
|
||||
'help-view': {template: '#help-view-template'},
|
||||
'cheat-sheet-view': {
|
||||
template: '#cheat-sheet-view-template',
|
||||
data: function () {return {showUnimplemented: false}}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
'config-changed': function () {this.modified = true;},
|
||||
'hostname-changed': function (hostname) {this.hostname = hostname},
|
||||
|
||||
|
||||
send: function (msg) {
|
||||
if (this.status == 'connected') {
|
||||
console.debug('>', msg);
|
||||
this.sock.send(msg);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
connected: function () {this.update()},
|
||||
update: function () {this.update()},
|
||||
|
||||
|
||||
check: function () {
|
||||
this.latestVersion = '';
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'https://https://raw.githubusercontent.com/OneFinityCNC/onefinity/master/latest.txt',
|
||||
data: {hid: this.state.hid},
|
||||
cache: false
|
||||
|
||||
}).done(function (data) {
|
||||
this.latestVersion = data;
|
||||
this.$broadcast('latest_version', data);
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
|
||||
upgrade: function () {
|
||||
this.password = '';
|
||||
this.confirmUpgrade = true;
|
||||
},
|
||||
|
||||
|
||||
upload: function (firmware) {
|
||||
this.firmware = firmware;
|
||||
this.firmwareName = firmware.name;
|
||||
this.password = '';
|
||||
this.confirmUpload = true;
|
||||
},
|
||||
|
||||
|
||||
error: function (msg) {
|
||||
// Honor user error blocking
|
||||
if (Date.now() - this.errorTimeoutStart < this.errorTimeout * 1000)
|
||||
return;
|
||||
|
||||
// Wait at least 1 sec to pop up repeated errors
|
||||
if (1 < msg.repeat && Date.now() - msg.ts < 1000) return;
|
||||
|
||||
// Popup error dialog
|
||||
this.errorShow = true;
|
||||
this.errorMessage = msg.msg;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
popupMessages: function () {
|
||||
var msgs = [];
|
||||
|
||||
for (var i = 0; i < this.state.messages.length; i++) {
|
||||
var text = this.state.messages[i].text;
|
||||
if (!/^#/.test(text)) msgs.push(text);
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {
|
||||
$(window).on('hashchange', this.parse_hash);
|
||||
this.connect();
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
metric: function () {return this.config.settings.units != 'IMPERIAL'},
|
||||
|
||||
|
||||
block_error_dialog: function () {
|
||||
this.errorTimeoutStart = Date.now();
|
||||
this.errorShow = false;
|
||||
},
|
||||
|
||||
|
||||
toggle_video: function (e) {
|
||||
if (this.video_size == 'small') this.video_size = 'large';
|
||||
else if (this.video_size == 'large') this.video_size = 'small';
|
||||
cookie.set('video-size', this.video_size);
|
||||
},
|
||||
|
||||
|
||||
toggle_crosshair: function (e) {
|
||||
e.preventDefault();
|
||||
this.crosshair = !this.crosshair;
|
||||
cookie.set('crosshair', this.crosshair);
|
||||
},
|
||||
|
||||
|
||||
estop: function () {
|
||||
if (this.state.xx == 'ESTOPPED') api.put('clear');
|
||||
else api.put('estop');
|
||||
},
|
||||
|
||||
|
||||
upgrade_confirmed: function () {
|
||||
this.confirmUpgrade = false;
|
||||
|
||||
api.put('upgrade', {password: this.password}).done(function () {
|
||||
this.firmwareUpgrading = true;
|
||||
|
||||
}.bind(this)).fail(function () {
|
||||
api.alert('Invalid password');
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
|
||||
upload_confirmed: function () {
|
||||
this.confirmUpload = false;
|
||||
|
||||
var form = new FormData();
|
||||
form.append('firmware', this.firmware);
|
||||
if (this.password) form.append('password', this.password);
|
||||
|
||||
$.ajax({
|
||||
url: '/api/firmware/update',
|
||||
type: 'PUT',
|
||||
data: form,
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false
|
||||
|
||||
}).success(function () {
|
||||
this.firmwareUpgrading = true;
|
||||
|
||||
}.bind(this)).error(function () {
|
||||
api.alert('Invalid password or bad firmware');
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
|
||||
show_upgrade: function () {
|
||||
if (!this.latestVersion) return false;
|
||||
return compare_versions(this.config.version, this.latestVersion) < 0;
|
||||
},
|
||||
|
||||
update: function () {
|
||||
api.get('config/load').done(function (config) {
|
||||
update_object(this.config, config, true);
|
||||
this.parse_hash();
|
||||
|
||||
if (!this.checkedUpgrade) {
|
||||
this.checkedUpgrade = true;
|
||||
|
||||
var check = this.config.admin['auto-check-upgrade'];
|
||||
if (typeof check == 'undefined' || check)
|
||||
this.$emit('check');
|
||||
}
|
||||
|
||||
this.check_ip_address();
|
||||
this.check_ssid();
|
||||
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
check_ip_address : function() {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'hostinfo.txt',
|
||||
data: {hid: this.state.hid},
|
||||
cache: false
|
||||
|
||||
}).done(function (data) {
|
||||
console.debug('>', data);
|
||||
this.ipAddress = data;
|
||||
this.$broadcast('ipAddress', data);
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
check_ssid : function() {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'ssidinfo.txt',
|
||||
data: {hid: this.state.hid},
|
||||
cache: false
|
||||
|
||||
}).done(function (data) {
|
||||
console.debug('>', data);
|
||||
this.wifiSSID = data;
|
||||
this.$broadcast('wifiSSID', data);
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
get_ip_address : function() {
|
||||
console.debug('get_ip>', this.ipAddress);
|
||||
return this.ipAddress;
|
||||
},
|
||||
|
||||
get_ssid : function() {
|
||||
console.debug('get_ssid>', this.wifiSSID);
|
||||
return this.wifiSSID;
|
||||
},
|
||||
|
||||
connect: function () {
|
||||
this.sock = new Sock('//' + window.location.host + '/sockjs');
|
||||
|
||||
this.sock.onmessage = function (e) {
|
||||
if (typeof e.data != 'object') return;
|
||||
|
||||
if ('log' in e.data) {
|
||||
this.$broadcast('log', e.data.log);
|
||||
delete e.data.log;
|
||||
}
|
||||
|
||||
// Check for session ID change on controller
|
||||
if ('sid' in e.data) {
|
||||
if (typeof this.sid == 'undefined') this.sid = e.data.sid;
|
||||
|
||||
else if (this.sid != e.data.sid) {
|
||||
if (typeof this.hostname != 'undefined' &&
|
||||
String(location.hostname) != 'localhost')
|
||||
location.hostname = this.hostname;
|
||||
location.reload(true);
|
||||
}
|
||||
}
|
||||
|
||||
update_object(this.state, e.data, false);
|
||||
this.$broadcast('update');
|
||||
|
||||
}.bind(this)
|
||||
|
||||
this.sock.onopen = function (e) {
|
||||
this.status = 'connected';
|
||||
this.$emit(this.status);
|
||||
this.$broadcast(this.status);
|
||||
}.bind(this)
|
||||
|
||||
this.sock.onclose = function (e) {
|
||||
this.status = 'disconnected';
|
||||
this.$emit(this.status);
|
||||
this.$broadcast(this.status);
|
||||
}.bind(this)
|
||||
},
|
||||
|
||||
|
||||
parse_hash: function () {
|
||||
var hash = location.hash.substr(1);
|
||||
|
||||
if (!hash.trim().length) {
|
||||
location.hash = 'control';
|
||||
return;
|
||||
}
|
||||
|
||||
var parts = hash.split(':');
|
||||
|
||||
if (parts.length == 2) this.index = parts[1];
|
||||
|
||||
this.currentView = parts[0];
|
||||
},
|
||||
|
||||
|
||||
save: function () {
|
||||
api.put('config/save', this.config).done(function (data) {
|
||||
this.modified = false;
|
||||
}.bind(this)).fail(function (error) {
|
||||
api.alert('Save failed', error);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
close_messages: function (action) {
|
||||
if (action == 'stop') api.put('stop');
|
||||
if (action == 'continue') api.put('unpause');
|
||||
|
||||
// Acknowledge messages
|
||||
if (this.state.messages.length) {
|
||||
var id = this.state.messages.slice(-1)[0].id
|
||||
api.put('message/' + id + '/ack');
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
65
src/js/axis-control.js
Normal file
65
src/js/axis-control.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#axis-control-template',
|
||||
props: ['axes', 'colors', 'enabled', 'adjust', 'step'],
|
||||
|
||||
|
||||
methods: {
|
||||
jog: function (axis, ring, direction) {
|
||||
var value = direction * this.value(ring);
|
||||
this.$dispatch(this.step ? 'step' : 'jog', this.axes[axis], value);
|
||||
},
|
||||
|
||||
back2zero: function(axis0,axis1) {
|
||||
this.$dispatch('back2zero',this.axes[axis0],this.axes[axis1])
|
||||
},
|
||||
|
||||
|
||||
release: function (axis) {
|
||||
if (!this.step) this.$dispatch('jog', this.axes[axis], 0)
|
||||
},
|
||||
|
||||
|
||||
value: function (ring) {
|
||||
var adjust = [0.01, 0.1, 1][this.adjust];
|
||||
if (this.step) return adjust * [0.1, 1, 10, 100][ring];
|
||||
return adjust * [0.1, 0.25, 0.5, 1][ring];
|
||||
},
|
||||
|
||||
|
||||
text: function (ring) {
|
||||
var value = this.value(ring) * (this.step ? 1 : 100);
|
||||
value = parseFloat(value.toFixed(3));
|
||||
return value + (this.step ? '' : '%');
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/js/axis-vars.js
Normal file
203
src/js/axis-vars.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
function is_defined(x) {return typeof x != 'undefined'}
|
||||
|
||||
|
||||
module.exports = {
|
||||
props: ['state', 'config'],
|
||||
|
||||
|
||||
computed: {
|
||||
x: function () {return this._compute_axis('x')},
|
||||
y: function () {return this._compute_axis('y')},
|
||||
z: function () {return this._compute_axis('z')},
|
||||
a: function () {return this._compute_axis('a')},
|
||||
b: function () {return this._compute_axis('b')},
|
||||
c: function () {return this._compute_axis('c')},
|
||||
axes: function () {return this._compute_axes()}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
_convert_length: function (value) {
|
||||
return this.state.imperial ? value / 25.4 : value;
|
||||
},
|
||||
|
||||
|
||||
_length_str: function (value) {
|
||||
return this._convert_length(value).toLocaleString() +
|
||||
(this.state.imperial ? ' in' : ' mm');
|
||||
},
|
||||
|
||||
|
||||
_compute_axis: function (axis) {
|
||||
var abs = this.state[axis + 'p'] || 0;
|
||||
var off = this.state['offset_' + axis];
|
||||
var motor_id = this._get_motor_id(axis);
|
||||
var motor = motor_id == -1 ? {} : this.config.motors[motor_id];
|
||||
var enabled = typeof motor.enabled != 'undefined' && motor.enabled;
|
||||
var homingMode = motor['homing-mode']
|
||||
var homed = this.state[motor_id + 'homed'];
|
||||
var min = this.state[motor_id + 'tn'];
|
||||
var max = this.state[motor_id + 'tm'];
|
||||
var dim = max - min;
|
||||
var pathMin = this.state['path_min_' + axis];
|
||||
var pathMax = this.state['path_max_' + axis];
|
||||
var pathDim = pathMax - pathMin;
|
||||
var under = pathMin + off < min;
|
||||
var over = max < pathMax + off;
|
||||
var klass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis;
|
||||
var state = 'UNHOMED';
|
||||
var icon = 'question-circle';
|
||||
var fault = this.state[motor_id + 'df'] & 0x1f;
|
||||
var shutdown = this.state.power_shutdown;
|
||||
var title;
|
||||
|
||||
if (fault || shutdown) {
|
||||
state = shutdown ? 'SHUTDOWN' : 'FAULT';
|
||||
klass += ' error';
|
||||
icon = 'exclamation-circle';
|
||||
|
||||
} else if (0 < dim && dim < pathDim) {
|
||||
state = 'NO FIT';
|
||||
klass += ' error';
|
||||
icon = 'ban';
|
||||
|
||||
} else if (homed) {
|
||||
state = 'HOMED'
|
||||
icon = 'check-circle';
|
||||
|
||||
if (over || under) {
|
||||
state = over ? 'OVER' : 'UNDER';
|
||||
klass += ' warn';
|
||||
icon = 'exclamation-circle';
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case 'UNHOMED': title = 'Click the home button to home axis.'; break;
|
||||
case 'HOMED': title = 'Axis successfuly homed.'; break;
|
||||
|
||||
case 'OVER':
|
||||
title = 'Tool path would move ' +
|
||||
this._length_str(pathMax + off - max) + ' beyond axis bounds.';
|
||||
break;
|
||||
|
||||
case 'UNDER':
|
||||
title = 'Tool path would move ' +
|
||||
this._length_str(min - pathMin - off) + ' below axis bounds.';
|
||||
break;
|
||||
|
||||
case 'NO FIT':
|
||||
title = 'Tool path dimensions exceed axis dimensions by ' +
|
||||
this._length_str(pathDim - dim) + '.';
|
||||
break;
|
||||
|
||||
case 'FAULT':
|
||||
title = 'Motor driver fault. A potentially damaging electrical ' +
|
||||
'condition was detected and the motor driver was shutdown. ' +
|
||||
'Please power down the controller and check your motor cabling. ' +
|
||||
'See the "Motor Faults" table on the "Indicators" tab for more ' +
|
||||
'information.';
|
||||
break;
|
||||
|
||||
case 'SHUTDOWN':
|
||||
title = 'Motor power fault. All motors in shutdown. ' +
|
||||
'See the "Power Faults" table on the "Indicators" tab for more ' +
|
||||
'information. Reboot controller to reset.';
|
||||
}
|
||||
|
||||
return {
|
||||
pos: abs - off,
|
||||
abs: abs,
|
||||
off: off,
|
||||
min: min,
|
||||
max: max,
|
||||
dim: dim,
|
||||
pathMin: pathMin,
|
||||
pathMax: pathMax,
|
||||
pathDim: pathDim,
|
||||
motor: motor_id,
|
||||
enabled: enabled,
|
||||
homingMode: homingMode,
|
||||
homed: homed,
|
||||
klass: klass,
|
||||
state: state,
|
||||
icon: icon,
|
||||
title: title
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_get_motor_id: function (axis) {
|
||||
for (var i = 0; i < this.config.motors.length; i++) {
|
||||
var motor = this.config.motors[i];
|
||||
if (motor.axis.toLowerCase() == axis) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
|
||||
|
||||
_compute_axes: function () {
|
||||
var homed = false;
|
||||
|
||||
for (var name of 'xyzabc') {
|
||||
var axis = this[name];
|
||||
|
||||
if (!axis.enabled) continue
|
||||
if (!axis.homed) {homed = false; break}
|
||||
homed = true;
|
||||
}
|
||||
|
||||
var error = false;
|
||||
var warn = false;
|
||||
|
||||
if (homed)
|
||||
for (name of 'xyzabc') {
|
||||
axis = this[name];
|
||||
|
||||
if (!axis.enabled) continue;
|
||||
if (axis.klass.indexOf('error') != -1) error = true;
|
||||
if (axis.klass.indexOf('warn') != -1) warn = true;
|
||||
}
|
||||
|
||||
var klass = homed ? 'homed' : 'unhomed';
|
||||
if (error) klass += ' error';
|
||||
else if (warn) klass += ' warn';
|
||||
|
||||
return {
|
||||
homed: homed,
|
||||
klass: klass
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/js/console.js
Normal file
87
src/js/console.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
function _msg_equal(a, b) {
|
||||
return a.level == b.level && a.source == b.source && a.where == b.where &&
|
||||
a.msg == b.msg;
|
||||
}
|
||||
|
||||
|
||||
// Shared among all instances
|
||||
var messages = [];
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#console-template',
|
||||
|
||||
|
||||
data: function () {
|
||||
return {messages: messages}
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
log: function (msg) {
|
||||
// There may be multiple instances of this module so ignore messages
|
||||
// that have already been processed.
|
||||
if (msg.logged) return;
|
||||
msg.logged = true;
|
||||
|
||||
// Make sure we have a message level
|
||||
msg.level = msg.level || 'info';
|
||||
|
||||
// Add to message log and count and collapse repeats
|
||||
var repeat = messages.length && _msg_equal(msg, messages[0]);
|
||||
if (repeat) messages[0].repeat++;
|
||||
else {
|
||||
msg.repeat = msg.repeat || 1;
|
||||
messages.unshift(msg);
|
||||
while (256 < messages.length) messages.pop();
|
||||
}
|
||||
msg.ts = Date.now();
|
||||
|
||||
// Write message to browser console for debugging
|
||||
var text = JSON.stringify(msg);
|
||||
if (msg.level == 'error' || msg.level == 'critical') console.error(text);
|
||||
else if (msg.level == 'warning') console.warn(text);
|
||||
else if (msg.level == 'debug' && console.debug) console.debug(text);
|
||||
else console.log(text);
|
||||
|
||||
// Event on errors
|
||||
if (msg.level == 'error' || msg.level == 'critical')
|
||||
this.$dispatch('error', msg);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
clear: function () {messages.splice(0, messages.length);},
|
||||
}
|
||||
}
|
||||
667
src/js/control-view.js
Normal file
667
src/js/control-view.js
Normal file
@@ -0,0 +1,667 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'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
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
|
||||
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.send('M70\nG90\nG0' + xcmd + ycmd + zcmd + acmd + '\nM72');
|
||||
},
|
||||
|
||||
probe_xyz() {
|
||||
var pcmd = "";
|
||||
var xoffset = this.config.probe["probe-xdim"];
|
||||
var yoffset = this.config.probe["probe-ydim"];
|
||||
var zoffset = this.config.probe["probe-zdim"];
|
||||
var fastSeek = this.config.probe["probe-fast-seek"];
|
||||
var slowSeek = this.config.probe["probe-slow-seek"];
|
||||
debugger;
|
||||
|
||||
if(this.mach_units == "METRIC") {
|
||||
|
||||
fastSeek = "F" + fastSeek;
|
||||
slowSeek = "F" + slowSeek;
|
||||
|
||||
//Metric Probing
|
||||
pcmd += "G92 X0\n";
|
||||
pcmd += "G92 Y0\n";
|
||||
pcmd += "G92 Z0\n";
|
||||
pcmd += "G21\n";
|
||||
pcmd += "G38.2 Z-25.4 " + fastSeek + "\n";
|
||||
pcmd += "G91 G0 Z1.5\n";
|
||||
pcmd += "G38.2 Z-2.5 " + slowSeek + "\n";
|
||||
|
||||
//var zoffset = 16.383;
|
||||
pcmd += "G92 Z " + zoffset + "\n";
|
||||
|
||||
pcmd += "G91 G0 Z 3.175\n";
|
||||
pcmd += "G91 G0 X 19.05\n";
|
||||
pcmd += "G91 G0 Z -12.7\n";
|
||||
pcmd += "G38.2 X -19.05 " + fastSeek + "\n";
|
||||
pcmd += "G91 G1 X 1.27 " + fastSeek +"\n";
|
||||
pcmd += "G38.2 X -4 " + slowSeek + "\n";
|
||||
|
||||
xoffset += this.tool_diameter/2.0;
|
||||
xoffset = xoffset.toFixed(5);
|
||||
pcmd += "G92 X " + xoffset + "\n";
|
||||
|
||||
pcmd += "G91 G0 X 2.5\n";
|
||||
pcmd += "G91 G0 Y 17\n";
|
||||
pcmd += "G91 G0 X -13\n";
|
||||
pcmd += "G38.2 Y -17 " + fastSeek + "\n";
|
||||
pcmd += "G91 G0 Y 1.27\n";
|
||||
pcmd += "G38.2 Y -4 " + slowSeek +"\n";
|
||||
|
||||
yoffset += this.tool_diameter/2.0;
|
||||
yoffset = yoffset.toFixed(5);
|
||||
pcmd += "G92 Y " + yoffset + "\n";
|
||||
|
||||
pcmd += "G91 G0 Y2.54\n";
|
||||
pcmd += "G91 G0 Z 25.4\n";
|
||||
pcmd += "G90 G0 X0 Y0\n";
|
||||
} else {
|
||||
|
||||
//Imperial Probing
|
||||
|
||||
xoffset = xoffset / 25.4;
|
||||
yoffset = yoffset / 25.4;
|
||||
zoffset = zoffset / 25.4;
|
||||
slowSeek = slowSeek / 25.4;
|
||||
slowSeek = slowSeek.toFixed(5);
|
||||
slowSeek = "F" + slowSeek;
|
||||
fastSeek = fastSeek / 25.4;
|
||||
fastSeek = fastSeek.toFixed(5);
|
||||
fastSeek = "F" + fastSeek;
|
||||
|
||||
pcmd += "G92 X0\n";
|
||||
pcmd += "G92 Y0\n";
|
||||
pcmd += "G92 Z0\n";
|
||||
pcmd += "G20\n";
|
||||
pcmd += "G38.2 Z-1.0 " + fastSeek + "\n";
|
||||
pcmd += "G91 G0 Z0.06\n";
|
||||
pcmd += "G38.2 Z-0.1 " + slowSeek + "\n";
|
||||
|
||||
//var zoffset = 0.645;
|
||||
zoffset = zoffset.toFixed(5);
|
||||
pcmd += "G92 Z " + zoffset + "\n";
|
||||
|
||||
|
||||
pcmd += "G91 G0 Z 0.125\n";
|
||||
pcmd += "G91 G0 X 0.75\n";
|
||||
pcmd += "G91 G0 Z -0.5\n";
|
||||
pcmd += "G38.2 X -0.75 " + fastSeek + "\n";
|
||||
pcmd += "G91 G1 X 0.05 " + fastSeek + "\n";
|
||||
pcmd += "G38.2 X -0.15 " + slowSeek + "\n";
|
||||
|
||||
xoffset += this.tool_diameter/2.0;
|
||||
xoffset = xoffset.toFixed(5);
|
||||
pcmd += "G92 X " + xoffset + "\n";
|
||||
|
||||
pcmd += "G91 G0 X 0.1\n";
|
||||
pcmd += "G91 G0 Y 0.75\n";
|
||||
pcmd += "G91 G0 X -0.5\n";
|
||||
pcmd += "G38.2 Y -0.75 " + fastSeek + "\n";
|
||||
pcmd += "G91 G0 Y 0.05\n";
|
||||
pcmd += "G38.2 Y -0.15 " + slowSeek +"\n";
|
||||
|
||||
yoffset += this.tool_diameter/2.0;
|
||||
yoffset = yoffset.toFixed(5);
|
||||
pcmd += "G92 Y " + yoffset + "\n";
|
||||
|
||||
pcmd += "G91 G0 Y0.1\n";
|
||||
pcmd += "G91 G0 Z1\n";
|
||||
pcmd += "G90 G0 X0 Y0\n";
|
||||
}
|
||||
|
||||
this.send(pcmd);
|
||||
|
||||
},
|
||||
|
||||
probe_z() {
|
||||
var pcmd = "";
|
||||
var fastSeek = this.config.probe["probe-fast-seek"];
|
||||
var slowSeek = this.config.probe["probe-slow-seek"];
|
||||
var zoffset = this.config.probe["probe-zdim"];
|
||||
|
||||
debugger;
|
||||
|
||||
if(this.mach_units == "METRIC") {
|
||||
fastSeek = "F" + fastSeek;
|
||||
slowSeek = "F" + slowSeek;
|
||||
|
||||
|
||||
pcmd += "G92 Z0\n";
|
||||
pcmd += "G21\n";
|
||||
pcmd += "G38.2 Z-25 " + fastSeek + "\n";
|
||||
pcmd += "G91 G0 Z1.5\n";
|
||||
pcmd += "G38.2 Z-2.5 " + slowSeek + "\n";
|
||||
pcmd += "G92 Z " + zoffset + "\n";
|
||||
pcmd += "G91 G0 Z3\n";
|
||||
|
||||
|
||||
} else {
|
||||
zoffset = zoffset / 25.4;
|
||||
slowSeek = slowSeek / 25.4;
|
||||
slowSeek = slowSeek.toFixed(5);
|
||||
slowSeek = "F" + slowSeek;
|
||||
fastSeek = fastSeek / 25.4;
|
||||
fastSeek = fastSeek.toFixed(5);
|
||||
fastSeek = "F" + fastSeek;
|
||||
|
||||
pcmd += "G92 Z0\n";
|
||||
pcmd += "G20\n";
|
||||
pcmd += "G38.2 Z-1.0 " + fastSeek +"\n";
|
||||
pcmd += "G91 G0 Z0.06\n";
|
||||
pcmd += "G38.2 Z-0.1 " + slowSeek + "\n";
|
||||
zoffset = zoffset.toFixed(5);
|
||||
pcmd += "G92 Z " + zoffset + "\n";
|
||||
pcmd += "G91 G0 Z0.125\n";
|
||||
|
||||
}
|
||||
|
||||
this.send(pcmd);
|
||||
|
||||
},
|
||||
|
||||
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('M70\nG91\nG0' + xcmd + ycmd + zcmd + acmd + '\nM72');
|
||||
},
|
||||
|
||||
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.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.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) {
|
||||
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;
|
||||
},
|
||||
|
||||
|
||||
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')]
|
||||
}
|
||||
69
src/js/cookie.js
Normal file
69
src/js/cookie.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/******************************************************************************\
|
||||
|
||||
Copyright 2018. Buildbotics LLC
|
||||
All Rights Reserved.
|
||||
|
||||
For information regarding this software email:
|
||||
Joseph Coffland
|
||||
joseph@buildbotics.com
|
||||
|
||||
This software is free software: you clan redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public License
|
||||
as published by the Free Software Foundation, either version 2.1 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This 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 C! library. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = function (prefix) {
|
||||
if (typeof prefix == 'undefined') prefix = '';
|
||||
|
||||
var cookie = {
|
||||
get: function (name, defaultValue) {
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(';');
|
||||
name = prefix + name + '=';
|
||||
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1);
|
||||
if (!c.indexOf(name)) return c.substring(name.length, c.length);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
},
|
||||
|
||||
|
||||
set: function (name, value, days) {
|
||||
var offset = 2147483647; // Max value
|
||||
if (typeof days != 'undefined') offset = days * 24 * 60 * 60 * 1000;
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + offset);
|
||||
var expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = prefix + name + '=' + value + ';' + expires + ';path=/';
|
||||
},
|
||||
|
||||
|
||||
set_bool: function (name, value) {
|
||||
cookie.set(name, value ? 'true' : 'false');
|
||||
},
|
||||
|
||||
|
||||
get_bool: function (name, defaultValue) {
|
||||
return cookie.get(name, defaultValue ? 'true' : 'false') == 'true';
|
||||
}
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
172
src/js/gcode-viewer.js
Normal file
172
src/js/gcode-viewer.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
var api = require('./api');
|
||||
|
||||
|
||||
var entityMap = {
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
||||
'/': '/', '`': '`', '=': '='}
|
||||
|
||||
|
||||
function escapeHTML(s) {
|
||||
return s.replace(/[&<>"'`=\/]/g, function (c) {return entityMap[c]})
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#gcode-viewer-template',
|
||||
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
empty: true,
|
||||
file: '',
|
||||
line: -1,
|
||||
scrolling: false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
'gcode-load': function (file) {this.load(file)},
|
||||
'gcode-clear': function () {this.clear()},
|
||||
'gcode-reload': function (file) {this.reload(file)},
|
||||
'gcode-line': function (line) {this.update_line(line)}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {
|
||||
this.clusterize = new Clusterize({
|
||||
rows: [],
|
||||
scrollElem: $(this.$el).find('.clusterize-scroll')[0],
|
||||
contentElem: $(this.$el).find('.clusterize-content')[0],
|
||||
no_data_text: 'GCode view...',
|
||||
callbacks: {clusterChanged: this.highlight}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
attached: function () {
|
||||
if (typeof this.clusterize != 'undefined')
|
||||
this.clusterize.refresh(true);
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
load: function (file) {
|
||||
if (file == this.file) return;
|
||||
this.clear();
|
||||
this.file = file;
|
||||
|
||||
if (!file) return;
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/api/file/' + file + '?' + Math.random(), true);
|
||||
xhr.responseType = 'text';
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.file != file) return;
|
||||
var lines = escapeHTML(xhr.response.trimRight()).split(/\r?\n/);
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
lines[i] = '<li class="ln' + (i + 1) + '">' +
|
||||
'<b>' + (i + 1) + '</b>' + lines[i] + '</li>';
|
||||
}
|
||||
|
||||
this.clusterize.update(lines);
|
||||
this.empty = false;
|
||||
|
||||
Vue.nextTick(this.update_line);
|
||||
}.bind(this)
|
||||
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
|
||||
clear: function () {
|
||||
this.empty = true;
|
||||
this.file = '';
|
||||
this.line = -1;
|
||||
this.clusterize.clear();
|
||||
},
|
||||
|
||||
|
||||
reload: function (file) {
|
||||
if (file != this.file) return;
|
||||
this.clear();
|
||||
this.load(file);
|
||||
},
|
||||
|
||||
|
||||
highlight: function () {
|
||||
var e = $(this.$el).find('.highlight');
|
||||
if (e.length) e.removeClass('highlight');
|
||||
|
||||
e = $(this.$el).find('.ln' + this.line);
|
||||
if (e.length) e.addClass('highlight');
|
||||
},
|
||||
|
||||
|
||||
update_line: function(line) {
|
||||
if (typeof line != 'undefined') {
|
||||
if (this.line == line) return;
|
||||
this.line = line;
|
||||
|
||||
} else line = this.line;
|
||||
|
||||
var totalLines = this.clusterize.getRowsAmount();
|
||||
|
||||
if (line <= 0) line = 1;
|
||||
if (totalLines < line) line = totalLines;
|
||||
|
||||
var e = $(this.$el).find('.clusterize-scroll');
|
||||
|
||||
var lineHeight = e[0].scrollHeight / totalLines;
|
||||
var linesPerPage = Math.floor(e[0].clientHeight / lineHeight);
|
||||
var current = e[0].scrollTop / lineHeight;
|
||||
var target = line - 1 - Math.floor(linesPerPage / 2);
|
||||
|
||||
// Update scroll position
|
||||
if (!this.scrolling) {
|
||||
if (target < current - 20 || current + 20 < target)
|
||||
e[0].scrollTop = target * lineHeight;
|
||||
|
||||
else {
|
||||
this.scrolling = true;
|
||||
e.animate({scrollTop: target * lineHeight}, {
|
||||
complete: function () {this.scrolling = false}.bind(this)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.nextTick(this.highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/js/helvetiker_regular.typeface.json
Normal file
1
src/js/helvetiker_regular.typeface.json
Normal file
File diff suppressed because one or more lines are too long
107
src/js/indicators.js
Normal file
107
src/js/indicators.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
var modbus = require('./modbus.js');
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#indicators-template',
|
||||
props: ['state'],
|
||||
|
||||
|
||||
computed: {
|
||||
modbus_status: function () {return modbus.status_to_string(this.state.mx)},
|
||||
|
||||
|
||||
sense_error: function () {
|
||||
var error = '';
|
||||
|
||||
if (this.state.motor_voltage_sense_error) error += 'Motor voltage\n';
|
||||
if (this.state.motor_current_sense_error) error += 'Motor current\n';
|
||||
if (this.state.load1_sense_error) error += 'Load 1\n';
|
||||
if (this.state.load2_sense_error) error += 'Load 2\n';
|
||||
if (this.state.vdd_current_sense_error) error += 'Vdd current\n';
|
||||
|
||||
return error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
is_motor_enabled: function (motor) {
|
||||
return typeof this.state[motor + 'me'] != 'undefined' &&
|
||||
this.state[motor + 'me'];
|
||||
},
|
||||
|
||||
|
||||
get_min_pin: function (motor) {
|
||||
switch (motor) {
|
||||
case 0: return 3;
|
||||
case 1: return 5;
|
||||
case 2: return 9;
|
||||
case 3: return 11;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
get_max_pin: function (motor) {
|
||||
switch (motor) {
|
||||
case 0: return 4;
|
||||
case 1: return 8;
|
||||
case 2: return 10;
|
||||
case 3: return 12;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
motor_fault_class: function (motor, bit) {
|
||||
if (typeof motor == 'undefined') {
|
||||
var status = this.state['fa'];
|
||||
if (typeof status == 'undefined') return 'fa-question';
|
||||
return 'fa-thumbs-' + (status ? 'down error' : 'up success')
|
||||
}
|
||||
|
||||
var flags = this.state[motor + 'df'];
|
||||
if (typeof flags == 'undefined') return 'fa-question';
|
||||
return (flags & (1 << bit)) ? 'fa-thumbs-down error' :
|
||||
'fa-thumbs-up success';
|
||||
},
|
||||
|
||||
|
||||
motor_reset: function (motor) {
|
||||
if (typeof motor == 'undefined') {
|
||||
var cmd = '';
|
||||
for (var i = 0; i < 4; i++)
|
||||
cmd += '\\$' + i + 'df=0\n';
|
||||
this.$dispatch('send', cmd);
|
||||
|
||||
} else this.$dispatch('send', '\\$' + motor + 'df=0');
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/js/io-indicator.js
Normal file
177
src/js/io-indicator.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: "#io-indicator-template",
|
||||
props: ['name', 'state'],
|
||||
|
||||
|
||||
computed: {
|
||||
klass: function () {
|
||||
if (this.name == 'min-switch-0') return this.get_motor_min_class(0);
|
||||
if (this.name == 'min-switch-1') return this.get_motor_min_class(1);
|
||||
if (this.name == 'min-switch-2') return this.get_motor_min_class(2);
|
||||
if (this.name == 'min-switch-3') return this.get_motor_min_class(3);
|
||||
if (this.name == 'max-switch-0') return this.get_motor_max_class(0);
|
||||
if (this.name == 'max-switch-1') return this.get_motor_max_class(1);
|
||||
if (this.name == 'max-switch-2') return this.get_motor_max_class(2);
|
||||
if (this.name == 'max-switch-3') return this.get_motor_max_class(3);
|
||||
if (this.name == 'estop') return this.get_input_class('ew', 'et');
|
||||
if (this.name == 'probe') return this.get_input_class('pw', 'pt');
|
||||
if (this.name == 'load-1') return this.get_output_class('1');
|
||||
if (this.name == 'load-2') return this.get_output_class('2');
|
||||
if (this.name == 'fault') return this.get_output_class('f');
|
||||
if (this.name == 'tool-enable-mode') return this.get_output_class('e');
|
||||
if (this.name == 'tool-direction-mode') return this.get_output_class('d');
|
||||
},
|
||||
|
||||
|
||||
tooltip: function () {
|
||||
if (this.name == 'min-switch-0') return this.get_motor_min_tooltip(0);
|
||||
if (this.name == 'min-switch-1') return this.get_motor_min_tooltip(1);
|
||||
if (this.name == 'min-switch-2') return this.get_motor_min_tooltip(2);
|
||||
if (this.name == 'min-switch-3') return this.get_motor_min_tooltip(3);
|
||||
if (this.name == 'max-switch-0') return this.get_motor_max_tooltip(0);
|
||||
if (this.name == 'max-switch-1') return this.get_motor_max_tooltip(1);
|
||||
if (this.name == 'max-switch-2') return this.get_motor_max_tooltip(2);
|
||||
if (this.name == 'max-switch-3') return this.get_motor_max_tooltip(3);
|
||||
if (this.name == 'estop') return this.get_input_tooltip('ew', 'et');
|
||||
if (this.name == 'probe') return this.get_input_tooltip('pw', 'pt');
|
||||
if (this.name == 'load-1') return this.get_output_tooltip('1');
|
||||
if (this.name == 'load-2') return this.get_output_tooltip('2');
|
||||
if (this.name == 'fault') return this.get_output_tooltip('f');
|
||||
if (this.name == 'tool-direction-mode')
|
||||
return this.get_output_tooltip('d');
|
||||
if (this.name == 'tool-enable-mode')
|
||||
return this.get_output_tooltip('e');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
get_io_state_class: function (active, state) {
|
||||
if (typeof active == 'undefined' || typeof state == 'undefined')
|
||||
return 'fa-exclamation-triangle warn';
|
||||
|
||||
if (state == 2) return 'fa-circle-o';
|
||||
|
||||
return (state ? 'fa-plus-circle' : 'fa-minus-circle') + ' ' +
|
||||
(active ? 'active' : 'inactive');
|
||||
},
|
||||
|
||||
|
||||
get_input_active: function (stateCode, typeCode) {
|
||||
var type = this.state[typeCode];
|
||||
var state = this.state[stateCode];
|
||||
|
||||
if (type == 1) return !state; // Normally open
|
||||
else if (type == 2) return state; // Normally closed
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
|
||||
get_input_class: function (stateCode, typeCode) {
|
||||
return this.get_io_state_class(this.get_input_active(stateCode, typeCode),
|
||||
this.state[stateCode]);
|
||||
},
|
||||
|
||||
|
||||
get_output_class: function (output) {
|
||||
return this.get_io_state_class(this.state[output + 'oa'],
|
||||
this.state[output + 'os']);
|
||||
},
|
||||
|
||||
|
||||
get_motor_min_class: function (motor) {
|
||||
return this.get_input_class(motor + 'lw', motor + 'ls');
|
||||
},
|
||||
|
||||
|
||||
get_motor_max_class: function (motor) {
|
||||
return this.get_input_class(motor + 'xw', motor + 'xs');
|
||||
},
|
||||
|
||||
|
||||
get_tooltip: function (mode, active, state) {
|
||||
if (typeof mode == 'undefined' || typeof active == 'undefined' ||
|
||||
typeof state == 'undefined') return 'Invalid';
|
||||
|
||||
if (state == 0) state = 'Lo/Gnd';
|
||||
else if (state == 1) state = 'Hi/+3.3v';
|
||||
else if (state == 2) state = 'Tristated';
|
||||
else return 'Invalid';
|
||||
|
||||
return 'Mode: ' + mode + '\nActive: ' + (active ? 'True' : 'False') +
|
||||
'\nLevel: ' + state;
|
||||
},
|
||||
|
||||
|
||||
get_input_tooltip: function (stateCode, typeCode) {
|
||||
var type = this.state[typeCode];
|
||||
if (type == 0) return 'Disabled';
|
||||
else if (type == 1) type = 'Normally open';
|
||||
else if (type == 2) type = 'Normally closed';
|
||||
|
||||
var active = this.get_input_active(stateCode, typeCode);
|
||||
var state = this.state[stateCode];
|
||||
|
||||
return this.get_tooltip(type, active, state);
|
||||
},
|
||||
|
||||
|
||||
get_output_tooltip: function (output) {
|
||||
var mode = this.state[output + 'om'];
|
||||
if (mode == 0) return 'Disabled';
|
||||
else if (mode == 1) mode = 'Lo/Hi';
|
||||
else if (mode == 2) mode = 'Hi/Lo';
|
||||
else if (mode == 3) mode = 'Tri/Lo';
|
||||
else if (mode == 4) mode = 'Tri/Hi';
|
||||
else if (mode == 5) mode = 'Lo/Tri';
|
||||
else if (mode == 6) mode = 'Hi/Tri';
|
||||
else mode = undefined;
|
||||
|
||||
var active = this.state[output + 'oa'];
|
||||
var state = this.state[output + 'os'];
|
||||
|
||||
return this.get_tooltip(mode, active, state);
|
||||
},
|
||||
|
||||
|
||||
get_motor_min_tooltip: function (motor) {
|
||||
return this.get_input_tooltip(motor + 'lw', motor + 'ls');
|
||||
},
|
||||
|
||||
|
||||
get_motor_max_tooltip: function (motor) {
|
||||
return this.get_input_tooltip(motor + 'xw', motor + 'xs');
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/js/io-view.js
Normal file
42
src/js/io-view.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#io-view-template',
|
||||
props: ['config', 'template', 'state'],
|
||||
|
||||
|
||||
events: {
|
||||
'input-changed': function() {
|
||||
this.$dispatch('config-changed');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/js/main.js
Normal file
147
src/js/main.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
function cookie_get(name) {
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(';');
|
||||
name = name + '=';
|
||||
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1);
|
||||
if (!c.indexOf(name)) return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cookie_set(name, value, days) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
var expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = name + '=' + value + ';' + expires + ';path=/';
|
||||
}
|
||||
|
||||
|
||||
var uuid_chars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+';
|
||||
|
||||
|
||||
function uuid(length) {
|
||||
if (typeof length == 'undefined') length = 52;
|
||||
|
||||
var s = '';
|
||||
for (var i = 0; i < length; i++)
|
||||
s += uuid_chars[Math.floor(Math.random() * uuid_chars.length)];
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
$(function() {
|
||||
if (typeof cookie_get('client-id') == 'undefined')
|
||||
cookie_set('client-id', uuid(), 10000);
|
||||
|
||||
// Vue debugging
|
||||
Vue.config.debug = true;
|
||||
//Vue.util.warn = function (msg) {console.debug('[Vue warn]: ' + msg)}
|
||||
|
||||
// Register global components
|
||||
Vue.component('templated-input', require('./templated-input'));
|
||||
Vue.component('message', require('./message'));
|
||||
Vue.component('indicators', require('./indicators'));
|
||||
Vue.component('io-indicator', require('./io-indicator'));
|
||||
Vue.component('console', require('./console'));
|
||||
Vue.component('unit-value', require('./unit-value'));
|
||||
|
||||
Vue.filter('number', function (value) {
|
||||
if (isNaN(value)) return 'NaN';
|
||||
return value.toLocaleString();
|
||||
});
|
||||
|
||||
Vue.filter('percent', function (value, precision) {
|
||||
if (typeof value == 'undefined') return '';
|
||||
if (typeof precision == 'undefined') precision = 2;
|
||||
return (value * 100.0).toFixed(precision) + '%';
|
||||
});
|
||||
|
||||
Vue.filter('non_zero_percent', function (value, precision) {
|
||||
if (!value) return '';
|
||||
if (typeof precision == 'undefined') precision = 2;
|
||||
return (value * 100.0).toFixed(precision) + '%';
|
||||
});
|
||||
|
||||
Vue.filter('fixed', function (value, precision) {
|
||||
if (typeof value == 'undefined') return '0';
|
||||
return parseFloat(value).toFixed(precision)
|
||||
});
|
||||
|
||||
Vue.filter('upper', function (value) {
|
||||
if (typeof value == 'undefined') return '';
|
||||
return value.toUpperCase()
|
||||
});
|
||||
|
||||
Vue.filter('time', function (value, precision) {
|
||||
if (isNaN(value)) return '';
|
||||
if (isNaN(precision)) precision = 0;
|
||||
|
||||
var MIN = 60;
|
||||
var HR = MIN * 60;
|
||||
var DAY = HR * 24;
|
||||
var parts = [];
|
||||
|
||||
if (DAY <= value) {
|
||||
parts.push(Math.floor(value / DAY));
|
||||
value %= DAY;
|
||||
}
|
||||
|
||||
if (HR <= value) {
|
||||
parts.push(Math.floor(value / HR));
|
||||
value %= HR;
|
||||
}
|
||||
|
||||
if (MIN <= value) {
|
||||
parts.push(Math.floor(value / MIN));
|
||||
value %= MIN;
|
||||
|
||||
} else parts.push(0);
|
||||
|
||||
parts.push(value);
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
parts[i] = parts[i].toFixed(i == parts.length - 1 ? precision : 0);
|
||||
if (i && parts[i] < 10) parts[i] = '0' + parts[i];
|
||||
}
|
||||
|
||||
return parts.join(':');
|
||||
});
|
||||
|
||||
// Vue app
|
||||
require('./app');
|
||||
});
|
||||
41
src/js/message.js
Normal file
41
src/js/message.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#message-template',
|
||||
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
twoWay: true
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/js/modbus-reg.js
Normal file
48
src/js/modbus-reg.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
replace: true,
|
||||
template: '#modbus-reg-view-template',
|
||||
props: ['index', 'model', 'template', 'enable'],
|
||||
|
||||
|
||||
computed: {
|
||||
has_user_value: function () {
|
||||
var type = this.model['reg-type'];
|
||||
return type.indexOf('write') != -1 || type.indexOf('fixed') != -1;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
change: function () {this.$dispatch('input-changed')}
|
||||
}
|
||||
}
|
||||
51
src/js/modbus.js
Normal file
51
src/js/modbus.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
// Must match modbus.c
|
||||
var exports = {
|
||||
DISCONNECTED: 0,
|
||||
OK: 1,
|
||||
CRC: 2,
|
||||
INVALID: 3,
|
||||
TIMEDOUT: 4
|
||||
};
|
||||
|
||||
|
||||
exports.status_to_string =
|
||||
function (status) {
|
||||
if (status == exports.OK) return 'Ok';
|
||||
if (status == exports.CRC) return 'CRC error';
|
||||
if (status == exports.INVALID) return 'Invalid response';
|
||||
if (status == exports.TIMEDOUT) return 'Timedout';
|
||||
return 'Disconnected';
|
||||
}
|
||||
|
||||
|
||||
module.exports = exports;
|
||||
136
src/js/motor-view.js
Normal file
136
src/js/motor-view.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#motor-view-template',
|
||||
props: ['index', 'config', 'template', 'state'],
|
||||
|
||||
|
||||
computed: {
|
||||
metric: function () {return this.$root.metric()},
|
||||
|
||||
|
||||
is_slave: function () {
|
||||
for (var i = 0; i < this.index; i++)
|
||||
if (this.motor.axis == this.config.motors[i].axis)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
motor: function () {return this.config.motors[this.index]},
|
||||
|
||||
|
||||
invalidMaxVelocity: function () {
|
||||
return this.maxMaxVelocity < this.motor['max-velocity'];
|
||||
},
|
||||
|
||||
|
||||
maxMaxVelocity: function () {
|
||||
return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3);
|
||||
},
|
||||
|
||||
|
||||
ustepPerSec: function () {
|
||||
return this.rpm * this.stepsPerRev * this.motor['microsteps'] / 60;
|
||||
},
|
||||
|
||||
|
||||
rpm: function () {
|
||||
return 1000 * this.motor['max-velocity'] / this.motor['travel-per-rev'];
|
||||
},
|
||||
|
||||
|
||||
gForce: function () {return this.motor['max-accel'] * 0.0283254504},
|
||||
gForcePerMin: function () {return this.motor['max-jerk'] * 0.0283254504},
|
||||
stepsPerRev: function () {return 360 / this.motor['step-angle']},
|
||||
|
||||
|
||||
umPerStep: function () {
|
||||
return this.motor['travel-per-rev'] * this.motor['step-angle'] / 0.36
|
||||
},
|
||||
|
||||
|
||||
milPerStep: function () {return this.umPerStep / 25.4},
|
||||
|
||||
invalidStallVelocity: function() {
|
||||
if(!this.motor['homing-mode'].startsWith('stall-')) return false;
|
||||
return this.maxStallVelocity < this.motor['search-velocity'];
|
||||
},
|
||||
|
||||
stallRPM: function() {
|
||||
var v = this.motor['search-velocity'];
|
||||
return 1000 * v / this.motor['travel-per-rev'];
|
||||
},
|
||||
|
||||
maxStallVelocity: function() {
|
||||
var maxRate = 900000/this.motor['stall-sample-time'];
|
||||
var ustep = this.motor['stall-microstep'];
|
||||
var angle = this.motor['step-angle'];
|
||||
var travel = this.motor['travel-per-rev'];
|
||||
var maxStall = maxRate * 60/ 360 /1000 * angle/ ustep * travel;
|
||||
|
||||
return 1 * maxStall.toFixed(3);
|
||||
},
|
||||
|
||||
stallUStepPerSec: function() {
|
||||
var ustep = this.motor['stall-microstep'];
|
||||
return this.stallRPM * this.stepsPerRev * ustep / 60;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
'input-changed': function() {
|
||||
Vue.nextTick(function () {
|
||||
// Limit max-velocity
|
||||
if (this.invalidMaxVelocity)
|
||||
this.$set('motor["max-velocity"]', this.maxMaxVelocity);
|
||||
|
||||
//Limit stall-velocity
|
||||
if(this.invalidStallVelocity)
|
||||
this.$set('motor["search-velocity"]', this.maxStallVelocity);
|
||||
|
||||
this.$dispatch('config-changed');
|
||||
}.bind(this))
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
show: function(name, templ) {
|
||||
if(templ.hmodes == undefined) return true;
|
||||
return templ.hmodes.indexOf(this.motor['homing-mode']) != -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
682
src/js/orbit.js
Normal file
682
src/js/orbit.js
Normal file
@@ -0,0 +1,682 @@
|
||||
/**
|
||||
* @author qiao / https://github.com/qiao
|
||||
* @author mrdoob / http://mrdoob.com
|
||||
* @author alteredq / http://alteredqualia.com/
|
||||
* @author WestLangley / http://github.com/WestLangley
|
||||
* @author erich666 / http://erichaines.com
|
||||
* @author jcoffland / https://buildbotics.com/
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
// This set of controls performs orbiting, dollying (zooming), and panning.
|
||||
// Unlike TrackballControls, it maintains the "up" direction object.up
|
||||
// (+Y by default).
|
||||
//
|
||||
// Orbit - left mouse / touch: one-finger move
|
||||
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
||||
// Pan - right mouse, or arrow keys / touch: two-finger move
|
||||
|
||||
|
||||
var OrbitControls = function (object, domElement) {
|
||||
this.object = object;
|
||||
this.domElement = domElement != undefined ? domElement : document;
|
||||
|
||||
// Set to false to disable this control
|
||||
this.enabled = true;
|
||||
|
||||
// "target" sets the location of focus, where the object orbits around
|
||||
this.target = new THREE.Vector3();
|
||||
|
||||
// How far you can zoom in and out (OrthographicCamera only)
|
||||
this.minZoom = 0;
|
||||
this.maxZoom = Infinity;
|
||||
|
||||
// How far you can orbit vertically, upper and lower limits.
|
||||
// Range is 0 to Math.PI radians.
|
||||
this.minPolarAngle = 0; // radians
|
||||
this.maxPolarAngle = Math.PI; // radians
|
||||
|
||||
// How far you can orbit horizontally, upper and lower limits.
|
||||
// If set, must be a sub-interval of the interval [- Math.PI, Math.PI].
|
||||
this.minAzimuthAngle = -Infinity; // radians
|
||||
this.maxAzimuthAngle = Infinity; // radians
|
||||
|
||||
// Set to true to enable damping (inertia)
|
||||
// If damping is enabled, call controls.update() in your animation loop
|
||||
this.enableDamping = false;
|
||||
this.dampingFactor = 0.25;
|
||||
|
||||
// This option enables dollying in and out;
|
||||
// left as "zoom" for backwards compatibility.
|
||||
// Set to false to disable zooming
|
||||
this.enableZoom = true;
|
||||
this.zoomSpeed = 1.0;
|
||||
|
||||
// Set to false to disable rotating
|
||||
this.enableRotate = true;
|
||||
this.rotateSpeed = 1.0;
|
||||
|
||||
// Set to false to disable panning
|
||||
this.enablePan = true;
|
||||
this.panSpeed = 1.0;
|
||||
this.screenSpacePanning = false; // if true, pan in screen-space
|
||||
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
||||
|
||||
// Set to true to automatically rotate around the target
|
||||
// If auto-rotate is enabled, call controls.update() in your animation loop
|
||||
this.autoRotate = false;
|
||||
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
|
||||
|
||||
// Set to false to disable use of the keys
|
||||
this.enableKeys = true;
|
||||
|
||||
// The four arrow keys
|
||||
this.keys = {LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40};
|
||||
|
||||
// Mouse buttons
|
||||
this.mouseButtons = {
|
||||
ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT
|
||||
};
|
||||
|
||||
// for reset
|
||||
this.target0 = this.target.clone();
|
||||
this.position0 = this.object.position.clone();
|
||||
this.zoom0 = this.object.zoom;
|
||||
|
||||
// public methods
|
||||
this.getPolarAngle = function () {return spherical.phi}
|
||||
this.getAzimuthalAngle = function () {return spherical.theta}
|
||||
|
||||
|
||||
this.saveState = function () {
|
||||
scope.target0.copy(scope.target);
|
||||
scope.position0.copy(scope.object.position);
|
||||
scope.zoom0 = scope.object.zoom;
|
||||
}
|
||||
|
||||
|
||||
this.reset = function () {
|
||||
scope.target.copy(scope.target0);
|
||||
scope.object.position.copy(scope.position0);
|
||||
scope.object.zoom = scope.zoom0;
|
||||
scope.object.updateProjectionMatrix();
|
||||
|
||||
scope.dispatchEvent(changeEvent);
|
||||
scope.update();
|
||||
|
||||
state = STATE.NONE;
|
||||
}
|
||||
|
||||
|
||||
this.update = function () {
|
||||
var offset = new THREE.Vector3();
|
||||
|
||||
// so camera.up is the orbit axis
|
||||
var quat = new THREE.Quaternion()
|
||||
.setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0));
|
||||
var quatInverse = quat.clone().inverse();
|
||||
|
||||
var lastPosition = new THREE.Vector3();
|
||||
var lastQuaternion = new THREE.Quaternion();
|
||||
|
||||
return function update() {
|
||||
var position = scope.object.position;
|
||||
|
||||
offset.copy(position).sub(scope.target);
|
||||
|
||||
// rotate offset to "y-axis-is-up" space
|
||||
offset.applyQuaternion(quat);
|
||||
|
||||
// angle from z-axis around y-axis
|
||||
spherical.setFromVector3(offset);
|
||||
|
||||
if (scope.autoRotate && state == STATE.NONE)
|
||||
rotateLeft(getAutoRotationAngle());
|
||||
|
||||
spherical.theta += sphericalDelta.theta;
|
||||
spherical.phi += sphericalDelta.phi;
|
||||
|
||||
// restrict theta to be between desired limits
|
||||
spherical.theta =
|
||||
Math.max(scope.minAzimuthAngle,
|
||||
Math.min(scope.maxAzimuthAngle, spherical.theta));
|
||||
|
||||
// restrict phi to be between desired limits
|
||||
spherical.phi =
|
||||
Math.max(scope.minPolarAngle,
|
||||
Math.min(scope.maxPolarAngle, spherical.phi));
|
||||
|
||||
spherical.makeSafe();
|
||||
spherical.radius *= scale;
|
||||
|
||||
// restrict radius to be between desired limits
|
||||
spherical.radius =
|
||||
Math.max(10, Math.min(scope.object.far * 0.8, spherical.radius));
|
||||
|
||||
// move target to panned location
|
||||
scope.target.add(panOffset);
|
||||
|
||||
offset.setFromSpherical(spherical);
|
||||
|
||||
// rotate offset back to "camera-up-vector-is-up" space
|
||||
offset.applyQuaternion(quatInverse);
|
||||
|
||||
position.copy(scope.target).add(offset);
|
||||
scope.object.lookAt(scope.target);
|
||||
|
||||
if (scope.enableDamping) {
|
||||
sphericalDelta.theta *= (1 - scope.dampingFactor);
|
||||
sphericalDelta.phi *= (1 - scope.dampingFactor);
|
||||
panOffset.multiplyScalar(1 - scope.dampingFactor);
|
||||
|
||||
} else {
|
||||
sphericalDelta.set(0, 0, 0);
|
||||
panOffset.set(0, 0, 0);
|
||||
}
|
||||
|
||||
// update condition is:
|
||||
// min(camera displacement, camera rotation in radians)^2 > EPS
|
||||
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
||||
if (zoomChanged || scale != 1 ||
|
||||
lastPosition.distanceToSquared(scope.object.position) > EPS ||
|
||||
8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {
|
||||
|
||||
scope.dispatchEvent(changeEvent);
|
||||
|
||||
lastPosition.copy(scope.object.position);
|
||||
lastQuaternion.copy(scope.object.quaternion);
|
||||
zoomChanged = false;
|
||||
scale = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
this.dispose = function () {
|
||||
scope.domElement.removeEventListener('contextmenu', onContextMenu, false);
|
||||
scope.domElement.removeEventListener('mousedown', onMouseDown, false);
|
||||
scope.domElement.removeEventListener('wheel', onMouseWheel, false);
|
||||
scope.domElement.removeEventListener('touchstart', onTouchStart, false);
|
||||
scope.domElement.removeEventListener('touchend', onTouchEnd, false);
|
||||
scope.domElement.removeEventListener('touchmove', onTouchMove, false);
|
||||
document.removeEventListener('mousemove', onMouseMove, false);
|
||||
document.removeEventListener('mouseup', onMouseUp, false);
|
||||
window.removeEventListener('keydown', onKeyDown, false);
|
||||
}
|
||||
|
||||
|
||||
// internals
|
||||
var scope = this;
|
||||
|
||||
var changeEvent = {type: 'change'};
|
||||
var startEvent = {type: 'start'};
|
||||
var endEvent = {type: 'end'};
|
||||
|
||||
var STATE = {
|
||||
NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4
|
||||
};
|
||||
|
||||
var state = STATE.NONE;
|
||||
var EPS = 0.000001;
|
||||
|
||||
// current position in spherical coordinates
|
||||
var spherical = new THREE.Spherical();
|
||||
var sphericalDelta = new THREE.Spherical();
|
||||
|
||||
var scale = 1;
|
||||
var panOffset = new THREE.Vector3();
|
||||
var zoomChanged = false;
|
||||
|
||||
var rotateStart = new THREE.Vector2();
|
||||
var rotateEnd = new THREE.Vector2();
|
||||
var rotateDelta = new THREE.Vector2();
|
||||
|
||||
var panStart = new THREE.Vector2();
|
||||
var panEnd = new THREE.Vector2();
|
||||
var panDelta = new THREE.Vector2();
|
||||
|
||||
var dollyStart = new THREE.Vector2();
|
||||
var dollyEnd = new THREE.Vector2();
|
||||
var dollyDelta = new THREE.Vector2();
|
||||
|
||||
|
||||
function getAutoRotationAngle() {
|
||||
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
|
||||
}
|
||||
|
||||
|
||||
function getZoomScale() {return Math.pow(0.95, scope.zoomSpeed)}
|
||||
function rotateLeft(angle) {sphericalDelta.theta -= angle}
|
||||
function rotateUp(angle) {sphericalDelta.phi -= angle}
|
||||
|
||||
|
||||
var panLeft = function () {
|
||||
var v = new THREE.Vector3();
|
||||
|
||||
return function panLeft(distance, objectMatrix) {
|
||||
v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
|
||||
v.multiplyScalar(-distance);
|
||||
panOffset.add(v);
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
var panUp = function () {
|
||||
var v = new THREE.Vector3();
|
||||
|
||||
return function panUp(distance, objectMatrix) {
|
||||
if (scope.screenSpacePanning) v.setFromMatrixColumn(objectMatrix, 1);
|
||||
else {
|
||||
v.setFromMatrixColumn(objectMatrix, 0);
|
||||
v.crossVectors(scope.object.up, v);
|
||||
}
|
||||
|
||||
v.multiplyScalar(distance);
|
||||
panOffset.add(v);
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
function unknownCamera() {
|
||||
console.warn('WARNING: OrbitControls.js encountered an unknown camera ' +
|
||||
'type - pan & zoom disabled.');
|
||||
scope.enablePan = false;
|
||||
scope.enableZoom = false;
|
||||
}
|
||||
|
||||
|
||||
// deltaX and deltaY are in pixels; right and down are positive
|
||||
var pan = function () {
|
||||
var offset = new THREE.Vector3();
|
||||
|
||||
return function pan(deltaX, deltaY) {
|
||||
var element = scope.domElement === document ?
|
||||
scope.domElement.body : scope.domElement;
|
||||
|
||||
if (scope.object.isPerspectiveCamera) {
|
||||
// perspective
|
||||
offset.copy(scope.object.position).sub(scope.target);
|
||||
var targetDistance = offset.length();
|
||||
|
||||
// half of the fov is center to top of screen
|
||||
targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0);
|
||||
|
||||
// we use only clientHeight here so aspect ratio does not distort speed
|
||||
panLeft(2 * deltaX * targetDistance / element.clientHeight,
|
||||
scope.object.matrix);
|
||||
panUp(2 * deltaY * targetDistance / element.clientHeight,
|
||||
scope.object.matrix);
|
||||
|
||||
} else if (scope.object.isOrthographicCamera) {
|
||||
// orthographic
|
||||
panLeft(deltaX * (scope.object.right - scope.object.left) /
|
||||
scope.object.zoom / element.clientWidth, scope.object.matrix);
|
||||
panUp(deltaY * (scope.object.top - scope.object.bottom) /
|
||||
scope.object.zoom / element.clientHeight, scope.object.matrix);
|
||||
|
||||
} else unknownCamera();
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
function dollyIn(dollyScale) {
|
||||
if (scope.object.isPerspectiveCamera) scale /= dollyScale;
|
||||
|
||||
else if (scope.object.isOrthographicCamera) {
|
||||
scope.object.zoom =
|
||||
Math.max(scope.minZoom,
|
||||
Math.min(scope.maxZoom, scope.object.zoom * dollyScale));
|
||||
scope.object.updateProjectionMatrix();
|
||||
zoomChanged = true;
|
||||
|
||||
} else unknownCamera();
|
||||
}
|
||||
|
||||
|
||||
function dollyOut(dollyScale) {
|
||||
if (scope.object.isPerspectiveCamera) scale *= dollyScale;
|
||||
|
||||
else if (scope.object.isOrthographicCamera) {
|
||||
scope.object.zoom =
|
||||
Math.max(scope.minZoom,
|
||||
Math.min(scope.maxZoom, scope.object.zoom / dollyScale));
|
||||
scope.object.updateProjectionMatrix();
|
||||
zoomChanged = true;
|
||||
|
||||
} else unknownCamera();
|
||||
}
|
||||
|
||||
|
||||
// event callbacks - update the object state
|
||||
function handleMouseDownRotate(event) {
|
||||
rotateStart.set(event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
|
||||
function handleMouseDownDolly(event) {
|
||||
dollyStart.set(event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
|
||||
function handleMouseDownPan(event) {
|
||||
panStart.set(event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
|
||||
function handleMouseMoveRotate(event) {
|
||||
rotateEnd.set(event.clientX, event.clientY);
|
||||
rotateDelta.subVectors(rotateEnd, rotateStart)
|
||||
.multiplyScalar(scope.rotateSpeed);
|
||||
|
||||
var element = scope.domElement === document ?
|
||||
scope.domElement.body : scope.domElement;
|
||||
|
||||
// yes, height
|
||||
rotateLeft(2 * Math.PI * rotateDelta.x / element.clientHeight);
|
||||
rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight);
|
||||
|
||||
rotateStart.copy(rotateEnd);
|
||||
|
||||
scope.update();
|
||||
}
|
||||
|
||||
|
||||
function handleMouseMoveDolly(event) {
|
||||
dollyEnd.set(event.clientX, event.clientY);
|
||||
dollyDelta.subVectors(dollyEnd, dollyStart);
|
||||
|
||||
if (dollyDelta.y > 0) dollyIn(getZoomScale());
|
||||
else if (dollyDelta.y < 0) dollyOut(getZoomScale());
|
||||
|
||||
dollyStart.copy(dollyEnd);
|
||||
scope.update();
|
||||
}
|
||||
|
||||
|
||||
function handleMouseMovePan(event) {
|
||||
panEnd.set(event.clientX, event.clientY);
|
||||
panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed);
|
||||
pan(panDelta.x, panDelta.y);
|
||||
panStart.copy(panEnd);
|
||||
scope.update();
|
||||
}
|
||||
|
||||
|
||||
function handleMouseUp(event) {}
|
||||
|
||||
|
||||
function handleMouseWheel(event) {
|
||||
if (event.deltaY < 0) dollyOut(getZoomScale());
|
||||
else if (event.deltaY > 0) dollyIn(getZoomScale());
|
||||
|
||||
scope.update();
|
||||
}
|
||||
|
||||
|
||||
function handleKeyDown(event) {
|
||||
switch (event.keyCode) {
|
||||
case scope.keys.UP:
|
||||
pan(0, scope.keyPanSpeed);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case scope.keys.BOTTOM:
|
||||
pan(0, -scope.keyPanSpeed);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case scope.keys.LEFT:
|
||||
pan(scope.keyPanSpeed, 0);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case scope.keys.RIGHT:
|
||||
pan(-scope.keyPanSpeed, 0);
|
||||
scope.update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleTouchStartRotate(event) {
|
||||
rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
|
||||
}
|
||||
|
||||
|
||||
function handleTouchStartDollyPan(event) {
|
||||
if (scope.enableZoom) {
|
||||
var dx = event.touches[0].pageX - event.touches[1].pageX;
|
||||
var dy = event.touches[0].pageY - event.touches[1].pageY;
|
||||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
dollyStart.set(0, distance);
|
||||
}
|
||||
|
||||
if (scope.enablePan) {
|
||||
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX);
|
||||
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY);
|
||||
panStart.set(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleTouchMoveRotate(event) {
|
||||
rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
|
||||
rotateDelta.subVectors(rotateEnd, rotateStart)
|
||||
.multiplyScalar(scope.rotateSpeed);
|
||||
|
||||
var element = scope.domElement === document ?
|
||||
scope.domElement.body : scope.domElement;
|
||||
|
||||
// yes, height
|
||||
rotateLeft(2 * Math.PI * rotateDelta.x / element.clientHeight);
|
||||
rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight);
|
||||
rotateStart.copy(rotateEnd);
|
||||
scope.update();
|
||||
}
|
||||
|
||||
|
||||
function handleTouchMoveDollyPan(event) {
|
||||
if (scope.enableZoom) {
|
||||
var dx = event.touches[0].pageX - event.touches[1].pageX;
|
||||
var dy = event.touches[0].pageY - event.touches[1].pageY;
|
||||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
dollyEnd.set(0, distance);
|
||||
dollyDelta.set(0, Math.pow(dollyEnd.y / dollyStart.y, scope.zoomSpeed));
|
||||
dollyIn(dollyDelta.y);
|
||||
dollyStart.copy(dollyEnd);
|
||||
}
|
||||
|
||||
|
||||
if (scope.enablePan) {
|
||||
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX);
|
||||
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY);
|
||||
|
||||
panEnd.set(x, y);
|
||||
panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed);
|
||||
pan(panDelta.x, panDelta.y);
|
||||
panStart.copy(panEnd);
|
||||
}
|
||||
|
||||
scope.update();
|
||||
}
|
||||
|
||||
|
||||
function handleTouchEnd(event) {}
|
||||
|
||||
|
||||
// event handlers - listen for events and reset state
|
||||
function onMouseDown(event) {
|
||||
if (!scope.enabled) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
switch (event.button) {
|
||||
case scope.mouseButtons.ORBIT:
|
||||
if (!scope.enableRotate) return;
|
||||
handleMouseDownRotate(event);
|
||||
state = STATE.ROTATE;
|
||||
break;
|
||||
|
||||
case scope.mouseButtons.ZOOM:
|
||||
if (!scope.enableZoom) return;
|
||||
handleMouseDownDolly(event);
|
||||
state = STATE.DOLLY;
|
||||
break;
|
||||
|
||||
case scope.mouseButtons.PAN:
|
||||
if (!scope.enablePan) return;
|
||||
handleMouseDownPan(event);
|
||||
state = STATE.PAN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (state != STATE.NONE) {
|
||||
document.addEventListener('mousemove', onMouseMove, false);
|
||||
document.addEventListener('mouseup', onMouseUp, false);
|
||||
scope.dispatchEvent(startEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onMouseMove(event) {
|
||||
if (!scope.enabled) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
switch (state) {
|
||||
case STATE.ROTATE:
|
||||
if (!scope.enableRotate) return;
|
||||
handleMouseMoveRotate(event);
|
||||
break;
|
||||
|
||||
case STATE.DOLLY:
|
||||
if (!scope.enableZoom) return;
|
||||
handleMouseMoveDolly(event);
|
||||
break;
|
||||
|
||||
case STATE.PAN:
|
||||
if (!scope.enablePan) return;
|
||||
handleMouseMovePan(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onMouseUp(event) {
|
||||
if (!scope.enabled) return;
|
||||
|
||||
handleMouseUp(event);
|
||||
document.removeEventListener('mousemove', onMouseMove, false);
|
||||
document.removeEventListener('mouseup', onMouseUp, false);
|
||||
scope.dispatchEvent(endEvent);
|
||||
state = STATE.NONE;
|
||||
}
|
||||
|
||||
|
||||
function onMouseWheel(event) {
|
||||
if (!scope.enabled || !scope.enableZoom ||
|
||||
(state != STATE.NONE && state != STATE.ROTATE)) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
scope.dispatchEvent(startEvent);
|
||||
handleMouseWheel(event);
|
||||
scope.dispatchEvent(endEvent);
|
||||
}
|
||||
|
||||
|
||||
function onKeyDown(event) {
|
||||
if (!scope.enabled || !scope.enableKeys || !scope.enablePan) return;
|
||||
|
||||
handleKeyDown(event);
|
||||
}
|
||||
|
||||
|
||||
function onTouchStart(event) {
|
||||
if (!scope.enabled) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
switch (event.touches.length) {
|
||||
case 1: // one-fingered touch: rotate
|
||||
if (!scope.enableRotate) return;
|
||||
handleTouchStartRotate(event);
|
||||
state = STATE.TOUCH_ROTATE;
|
||||
break;
|
||||
|
||||
case 2: // two-fingered touch: dolly-pan
|
||||
if (!scope.enableZoom && !scope.enablePan) return;
|
||||
handleTouchStartDollyPan(event);
|
||||
state = STATE.TOUCH_DOLLY_PAN;
|
||||
break;
|
||||
|
||||
default: state = STATE.NONE;
|
||||
}
|
||||
|
||||
if (state != STATE.NONE) scope.dispatchEvent(startEvent);
|
||||
}
|
||||
|
||||
|
||||
function onTouchMove(event) {
|
||||
if (!scope.enabled) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
switch (event.touches.length) {
|
||||
case 1: // one-fingered touch: rotate
|
||||
if (!scope.enableRotate) return;
|
||||
if (state != STATE.TOUCH_ROTATE) return; // is this needed?
|
||||
|
||||
handleTouchMoveRotate(event);
|
||||
break;
|
||||
|
||||
case 2: // two-fingered touch: dolly-pan
|
||||
if (!scope.enableZoom && !scope.enablePan) return;
|
||||
if (state != STATE.TOUCH_DOLLY_PAN) return; // is this needed?
|
||||
|
||||
handleTouchMoveDollyPan(event);
|
||||
break;
|
||||
|
||||
default: state = STATE.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onTouchEnd(event) {
|
||||
if (!scope.enabled) return;
|
||||
|
||||
handleTouchEnd(event);
|
||||
scope.dispatchEvent(endEvent);
|
||||
state = STATE.NONE;
|
||||
}
|
||||
|
||||
|
||||
function onContextMenu(event) {
|
||||
if (!scope.enabled) return;
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
scope.domElement.addEventListener('contextmenu', onContextMenu, false);
|
||||
scope.domElement.addEventListener('mousedown', onMouseDown, false);
|
||||
scope.domElement.addEventListener('wheel', onMouseWheel, false);
|
||||
scope.domElement.addEventListener('touchstart', onTouchStart, false);
|
||||
scope.domElement.addEventListener('touchend', onTouchEnd, false);
|
||||
scope.domElement.addEventListener('touchmove', onTouchMove, false);
|
||||
window .addEventListener('keydown', onKeyDown, false);
|
||||
|
||||
this.update(); // force an update at start
|
||||
}
|
||||
|
||||
|
||||
OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
|
||||
OrbitControls.prototype.constructor = OrbitControls;
|
||||
module.exports = OrbitControls;
|
||||
759
src/js/path-viewer.js
Normal file
759
src/js/path-viewer.js
Normal file
@@ -0,0 +1,759 @@
|
||||
/******************************************************************************\
|
||||
|
||||
Copyright 2018. Buildbotics LLC
|
||||
All Rights Reserved.
|
||||
|
||||
For information regarding this software email:
|
||||
Joseph Coffland
|
||||
joseph@buildbotics.com
|
||||
|
||||
This software is free software: you clan redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public License
|
||||
as published by the Free Software Foundation, either version 2.1 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This 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 C! library. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
var orbit = require('./orbit');
|
||||
var cookie = require('./cookie')('bbctrl-');
|
||||
var api = require('./api');
|
||||
var font = require('./helvetiker_regular.typeface.json')
|
||||
|
||||
|
||||
function get(obj, name, defaultValue) {
|
||||
return typeof obj[name] == 'undefined' ? defaultValue : obj[name];
|
||||
}
|
||||
|
||||
|
||||
var surfaceModes = ['cut', 'wire', 'solid', 'off'];
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#path-viewer-template',
|
||||
props: ['toolpath'],
|
||||
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
enabled: false,
|
||||
loading: false,
|
||||
dirty: true,
|
||||
snapView: cookie.get('snap-view', 'isometric'),
|
||||
small: cookie.get_bool('small-path-view', true),
|
||||
surfaceMode: 'cut',
|
||||
showPath: cookie.get_bool('show-path', true),
|
||||
showTool: cookie.get_bool('show-tool', true),
|
||||
showBBox: cookie.get_bool('show-bbox', true),
|
||||
showAxes: cookie.get_bool('show-axes', true),
|
||||
showIntensity: cookie.get_bool('show-intensity', false)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
target: function () {return $(this.$el).find('.path-viewer-content')[0]}
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
toolpath: function () {Vue.nextTick(this.update)},
|
||||
surfaceMode: function (mode) {this.update_surface_mode(mode)},
|
||||
|
||||
|
||||
small: function (enable) {
|
||||
cookie.set_bool('small-path-view', enable);
|
||||
Vue.nextTick(this.update_view)
|
||||
},
|
||||
|
||||
|
||||
showPath: function (enable) {
|
||||
cookie.set_bool('show-path', enable);
|
||||
this.set_visible(this.pathView, enable)
|
||||
},
|
||||
|
||||
|
||||
showTool: function (enable) {
|
||||
cookie.set_bool('show-tool', enable);
|
||||
this.set_visible(this.toolView, enable)
|
||||
},
|
||||
|
||||
|
||||
showAxes: function (enable) {
|
||||
cookie.set_bool('show-axes', enable);
|
||||
this.set_visible(this.axesView, enable)
|
||||
},
|
||||
|
||||
|
||||
showIntensity: function (enable) {
|
||||
cookie.set_bool('show-intensity', enable);
|
||||
Vue.nextTick(this.update)
|
||||
},
|
||||
|
||||
|
||||
showBBox: function (enable) {
|
||||
cookie.set_bool('show-bbox', enable);
|
||||
this.set_visible(this.bboxView, enable);
|
||||
this.set_visible(this.envelopeView, enable);
|
||||
},
|
||||
|
||||
|
||||
x: function () {this.axis_changed()},
|
||||
y: function () {this.axis_changed()},
|
||||
z: function () {this.axis_changed()}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {
|
||||
this.graphics();
|
||||
Vue.nextTick(this.update);
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
update: function () {
|
||||
if (!this.state.selected) {
|
||||
this.dirty = true;
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
} else if (!this.toolpath.filename && !this.loading) {
|
||||
this.loading = true;
|
||||
this.dirty = true;
|
||||
this.draw_loading();
|
||||
}
|
||||
|
||||
if (!this.enabled || !this.toolpath.filename) return;
|
||||
|
||||
function get(url) {
|
||||
var d = $.Deferred();
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('GET', url + '?' + Math.random(), true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (xhr.response) d.resolve(new Float32Array(xhr.response));
|
||||
else d.reject();
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
|
||||
return d.promise();
|
||||
}
|
||||
|
||||
var d1 = get('/api/path/' + this.toolpath.filename + '/positions');
|
||||
var d2 = get('/api/path/' + this.toolpath.filename + '/speeds');
|
||||
|
||||
$.when(d1, d2).done(function (positions, speeds) {
|
||||
this.positions = positions
|
||||
this.speeds = speeds;
|
||||
this.loading = false;
|
||||
|
||||
// Update scene
|
||||
this.scene = new THREE.Scene();
|
||||
this.draw(this.scene);
|
||||
this.snap(this.snapView);
|
||||
|
||||
this.update_view();
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
|
||||
update_surface_mode: function (mode) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
if (typeof this.surfaceMaterial != 'undefined') {
|
||||
this.surfaceMaterial.wireframe = mode == 'wire';
|
||||
this.surfaceMaterial.needsUpdate = true;
|
||||
}
|
||||
|
||||
this.set_visible(this.surfaceMesh, mode == 'cut' || mode == 'wire');
|
||||
this.set_visible(this.workpieceMesh, mode == 'solid');
|
||||
},
|
||||
|
||||
|
||||
load_surface: function (surface) {
|
||||
if (typeof surface == 'undefined') {
|
||||
this.vertices = undefined;
|
||||
this.normals = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.vertices = surface.vertices;
|
||||
|
||||
// Expand normals
|
||||
this.normals = [];
|
||||
for (var i = 0; i < surface.normals.length / 3; i++)
|
||||
for (var j = 0; j < 3; j++)
|
||||
for (var k = 0; k < 3; k++)
|
||||
this.normals.push(surface.normals[i * 3 + k]);
|
||||
},
|
||||
|
||||
|
||||
set_visible: function (target, visible) {
|
||||
if (typeof target != 'undefined') target.visible = visible;
|
||||
this.dirty = true;
|
||||
},
|
||||
|
||||
|
||||
get_dims: function () {
|
||||
var t = $(this.target);
|
||||
var width = t.innerWidth();
|
||||
var height = t.innerHeight();
|
||||
return {width: width, height: height};
|
||||
},
|
||||
|
||||
|
||||
update_view: function () {
|
||||
if (!this.enabled) return;
|
||||
var dims = this.get_dims();
|
||||
|
||||
this.camera.aspect = dims.width / dims.height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(dims.width, dims.height);
|
||||
|
||||
if (this.loading) {
|
||||
this.controls.reset();
|
||||
this.camera.position.copy(new THREE.Vector3(0, 0, 600));
|
||||
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
this.dirty = true;
|
||||
},
|
||||
|
||||
|
||||
update_tool: function (tool) {
|
||||
if (!this.enabled) return;
|
||||
if (typeof tool == 'undefined') tool = this.toolView;
|
||||
if (typeof tool == 'undefined') return;
|
||||
tool.position.x = this.x.pos;
|
||||
tool.position.y = this.y.pos;
|
||||
tool.position.z = this.z.pos;
|
||||
},
|
||||
|
||||
|
||||
update_envelope: function (envelope) {
|
||||
if (!this.enabled || !this.axes.homed) return;
|
||||
if (typeof envelope == 'undefined') envelope = this.envelopeView;
|
||||
if (typeof envelope == 'undefined') return;
|
||||
|
||||
var min = new THREE.Vector3();
|
||||
var max = new THREE.Vector3();
|
||||
|
||||
for (var axis of 'xyz') {
|
||||
min[axis] = this[axis].min - this[axis].off;
|
||||
max[axis] = this[axis].max - this[axis].off;
|
||||
}
|
||||
|
||||
var bounds = new THREE.Box3(min, max);
|
||||
if (bounds.isEmpty()) envelope.geometry = this.create_empty_geom();
|
||||
else envelope.geometry = this.create_bbox_geom(bounds);
|
||||
},
|
||||
|
||||
|
||||
axis_changed: function () {
|
||||
this.update_tool();
|
||||
this.update_envelope();
|
||||
this.dirty = true;
|
||||
},
|
||||
|
||||
|
||||
graphics: function () {
|
||||
try {
|
||||
// Renderer
|
||||
this.renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
this.renderer.setClearColor(0, 0);
|
||||
this.target.appendChild(this.renderer.domElement);
|
||||
|
||||
} catch (e) {
|
||||
console.log('WebGL not supported: ', e);
|
||||
return;
|
||||
}
|
||||
this.enabled = true;
|
||||
|
||||
// Camera
|
||||
this.camera = new THREE.PerspectiveCamera(45, 4 / 3, 1, 10000);
|
||||
|
||||
// Lighting
|
||||
this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
|
||||
|
||||
var keyLight = new THREE.DirectionalLight
|
||||
(new THREE.Color('hsl(30, 100%, 75%)'), 0.75);
|
||||
keyLight.position.set(-100, 0, 100);
|
||||
|
||||
var fillLight = new THREE.DirectionalLight
|
||||
(new THREE.Color('hsl(240, 100%, 75%)'), 0.25);
|
||||
fillLight.position.set(100, 0, 100);
|
||||
|
||||
var backLight = new THREE.DirectionalLight(0xffffff, 0.5);
|
||||
backLight.position.set(100, 0, -100).normalize();
|
||||
|
||||
this.lights = new THREE.Group();
|
||||
this.lights.add(keyLight);
|
||||
this.lights.add(fillLight);
|
||||
this.lights.add(backLight);
|
||||
|
||||
// Surface material
|
||||
this.surfaceMaterial = this.create_surface_material();
|
||||
|
||||
// Controls
|
||||
this.controls = new orbit(this.camera, this.renderer.domElement);
|
||||
this.controls.enableDamping = true;
|
||||
this.controls.dampingFactor = 0.2;
|
||||
this.controls.rotateSpeed = 0.25;
|
||||
this.controls.enableZoom = true;
|
||||
|
||||
// Move lights with scene
|
||||
this.controls.addEventListener('change', function (scope) {
|
||||
return function () {
|
||||
keyLight.position.copy(scope.camera.position);
|
||||
fillLight.position.copy(scope.camera.position);
|
||||
backLight.position.copy(scope.camera.position);
|
||||
keyLight.lookAt(scope.controls.target);
|
||||
fillLight.lookAt(scope.controls.target);
|
||||
backLight.lookAt(scope.controls.target);
|
||||
scope.dirty = true;
|
||||
}
|
||||
}(this))
|
||||
|
||||
// Events
|
||||
window.addEventListener('resize', this.update_view, false);
|
||||
|
||||
// Start it
|
||||
this.render();
|
||||
},
|
||||
|
||||
|
||||
create_surface_material: function () {
|
||||
return new THREE.MeshPhongMaterial({
|
||||
specular: 0x111111,
|
||||
shininess: 10,
|
||||
side: THREE.FrontSide,
|
||||
color: 0x0c2d53
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
draw_loading: function () {
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
var geometry = new THREE.TextGeometry('Loading 3D View...', {
|
||||
font: new THREE.Font(font),
|
||||
size: 40,
|
||||
height: 5,
|
||||
curveSegments: 12,
|
||||
bevelEnabled: true,
|
||||
bevelThickness: 10,
|
||||
bevelSize: 8,
|
||||
bevelSegments: 5
|
||||
});
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
var center = geometry.center();
|
||||
var mesh = new THREE.Mesh(geometry, this.surfaceMaterial);
|
||||
|
||||
this.scene.add(mesh);
|
||||
this.scene.add(this.ambient);
|
||||
this.scene.add(this.lights);
|
||||
this.update_view();
|
||||
},
|
||||
|
||||
|
||||
draw_workpiece: function (scene, material) {
|
||||
if (typeof this.workpiece == 'undefined') return;
|
||||
|
||||
var min = this.workpiece.min;
|
||||
var max = this.workpiece.max;
|
||||
|
||||
min = new THREE.Vector3(min[0], min[1], min[2]);
|
||||
max = new THREE.Vector3(max[0], max[1], max[2]);
|
||||
var dims = max.clone().sub(min);
|
||||
|
||||
var geometry = new THREE.BoxGeometry(dims.x, dims.y, dims.z)
|
||||
var mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
var offset = dims.clone();
|
||||
offset.divideScalar(2);
|
||||
offset.add(min);
|
||||
|
||||
mesh.position.add(offset);
|
||||
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
scene.add(mesh);
|
||||
|
||||
return mesh;
|
||||
},
|
||||
|
||||
|
||||
draw_surface: function (scene, material) {
|
||||
if (typeof this.vertices == 'undefined') return;
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
geometry.addAttribute
|
||||
('position', new THREE.Float32BufferAttribute(this.vertices, 3));
|
||||
geometry.addAttribute
|
||||
('normal', new THREE.Float32BufferAttribute(this.normals, 3));
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
return new THREE.Mesh(geometry, material);
|
||||
},
|
||||
|
||||
|
||||
draw_tool: function (scene, bbox) {
|
||||
// Tool size is relative to bounds
|
||||
var size = bbox.getSize(new THREE.Vector3());
|
||||
var length = (size.x + size.y + size.z) / 24;
|
||||
|
||||
if (length < 1) length = 1;
|
||||
|
||||
var material = new THREE.MeshPhongMaterial({
|
||||
transparent: true,
|
||||
opacity: 0.75,
|
||||
specular: 0x161616,
|
||||
shininess: 10,
|
||||
color: 0xffa500 // Orange
|
||||
});
|
||||
|
||||
var geometry = new THREE.CylinderGeometry(length / 2, 0, length, 128);
|
||||
geometry.translate(0, length / 2, 0);
|
||||
geometry.rotateX(0.5 * Math.PI);
|
||||
|
||||
var mesh = new THREE.Mesh(geometry, material);
|
||||
this.update_tool(mesh);
|
||||
mesh.visible = this.showTool;
|
||||
scene.add(mesh);
|
||||
return mesh;
|
||||
},
|
||||
|
||||
|
||||
draw_axis: function (axis, up, length, radius) {
|
||||
var color;
|
||||
|
||||
if (axis == 0) color = 0xff0000; // Red
|
||||
else if (axis == 1) color = 0x00ff00; // Green
|
||||
else if (axis == 2) color = 0x0000ff; // Blue
|
||||
|
||||
var group = new THREE.Group();
|
||||
var material = new THREE.MeshPhongMaterial({
|
||||
specular: 0x161616, shininess: 10, color: color
|
||||
});
|
||||
var geometry = new THREE.CylinderGeometry(radius, radius, length, 128);
|
||||
geometry.translate(0, -length / 2, 0);
|
||||
group.add(new THREE.Mesh(geometry, material));
|
||||
|
||||
geometry = new THREE.CylinderGeometry(1.5 * radius, 0, 2 * radius, 128);
|
||||
geometry.translate(0, -length - radius, 0);
|
||||
group.add(new THREE.Mesh(geometry, material));
|
||||
|
||||
if (axis == 0) group.rotateZ((up ? 0.5 : 1.5) * Math.PI);
|
||||
else if (axis == 1) group.rotateX((up ? 0 : 1 ) * Math.PI);
|
||||
else if (axis == 2) group.rotateX((up ? 1.5 : 0.5) * Math.PI);
|
||||
|
||||
return group;
|
||||
},
|
||||
|
||||
|
||||
draw_axes: function (scene, bbox) {
|
||||
var size = bbox.getSize(new THREE.Vector3());
|
||||
var length = (size.x + size.y + size.z) / 3;
|
||||
length /= 10;
|
||||
|
||||
if (length < 1) length = 1;
|
||||
|
||||
var radius = length / 20;
|
||||
|
||||
var group = new THREE.Group();
|
||||
|
||||
for (var axis = 0; axis < 3; axis++)
|
||||
for (var up = 0; up < 2; up++)
|
||||
group.add(this.draw_axis(axis, up, length, radius));
|
||||
|
||||
group.visible = this.showAxes;
|
||||
scene.add(group);
|
||||
|
||||
return group;
|
||||
},
|
||||
|
||||
|
||||
get_color: function (speed) {
|
||||
if (isNaN(speed)) return [255, 0, 0]; // Rapid
|
||||
|
||||
var intensity = speed / this.toolpath.maxSpeed;
|
||||
if (typeof speed == 'undefined' || !this.showIntensity) intensity = 1;
|
||||
return [0, 255 * intensity, 127 * (1 - intensity)];
|
||||
},
|
||||
|
||||
|
||||
draw_path: function (scene) {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
var material =
|
||||
new THREE.LineBasicMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
linewidth: 1.5
|
||||
});
|
||||
|
||||
var positions = new THREE.Float32BufferAttribute(this.positions, 3);
|
||||
geometry.addAttribute('position', positions);
|
||||
|
||||
var colors = [];
|
||||
for (var i = 0; i < this.speeds.length; i++) {
|
||||
var color = this.get_color(this.speeds[i]);
|
||||
Array.prototype.push.apply(colors, color);
|
||||
}
|
||||
|
||||
colors = new THREE.Uint8BufferAttribute(colors, 3, true);
|
||||
geometry.addAttribute('color', colors);
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
var line = new THREE.Line(geometry, material);
|
||||
|
||||
line.visible = this.showPath;
|
||||
scene.add(line);
|
||||
|
||||
return line;
|
||||
},
|
||||
|
||||
|
||||
create_empty_geom: function () {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
geometry.addAttribute('position',
|
||||
new THREE.Float32BufferAttribute([], 3));
|
||||
return geometry;
|
||||
},
|
||||
|
||||
|
||||
create_bbox_geom: function (bbox) {
|
||||
var vertices = [];
|
||||
|
||||
if (!bbox.isEmpty()) {
|
||||
// Top
|
||||
vertices.push(bbox.min.x, bbox.min.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.min.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.min.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.min.y, bbox.max.z);
|
||||
vertices.push(bbox.max.x, bbox.min.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.min.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.min.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.min.y, bbox.min.z);
|
||||
|
||||
// Bottom
|
||||
vertices.push(bbox.min.x, bbox.max.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.max.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.max.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.max.y, bbox.max.z);
|
||||
vertices.push(bbox.max.x, bbox.max.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.max.y, bbox.min.z);
|
||||
|
||||
// Sides
|
||||
vertices.push(bbox.min.x, bbox.min.y, bbox.min.z);
|
||||
vertices.push(bbox.min.x, bbox.max.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.min.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.max.y, bbox.min.z);
|
||||
vertices.push(bbox.max.x, bbox.min.y, bbox.max.z);
|
||||
vertices.push(bbox.max.x, bbox.max.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.min.y, bbox.max.z);
|
||||
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
|
||||
}
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
geometry.addAttribute('position',
|
||||
new THREE.Float32BufferAttribute(vertices, 3));
|
||||
|
||||
return geometry;
|
||||
},
|
||||
|
||||
|
||||
draw_bbox: function (scene, bbox) {
|
||||
var geometry = this.create_bbox_geom(bbox);
|
||||
var material = new THREE.LineBasicMaterial({color: 0xffffff});
|
||||
var line = new THREE.LineSegments(geometry, material);
|
||||
|
||||
line.visible = this.showBBox;
|
||||
|
||||
scene.add(line);
|
||||
|
||||
return line;
|
||||
},
|
||||
|
||||
|
||||
draw_envelope: function (scene) {
|
||||
var geometry = this.create_empty_geom();
|
||||
var material = new THREE.LineBasicMaterial({color: 0x00f7ff});
|
||||
var line = new THREE.LineSegments(geometry, material);
|
||||
|
||||
line.visible = this.showBBox;
|
||||
|
||||
scene.add(line);
|
||||
this.update_envelope(line);
|
||||
|
||||
return line;
|
||||
},
|
||||
|
||||
|
||||
draw: function (scene) {
|
||||
// Lights
|
||||
scene.add(this.ambient);
|
||||
scene.add(this.lights);
|
||||
|
||||
// Model
|
||||
this.pathView = this.draw_path(scene);
|
||||
this.surfaceMesh = this.draw_surface(scene, this.surfaceMaterial);
|
||||
this.workpieceMesh = this.draw_workpiece(scene, this.surfaceMaterial);
|
||||
this.update_surface_mode(this.surfaceMode);
|
||||
|
||||
// Compute bounding box
|
||||
var bbox = this.get_model_bounds();
|
||||
|
||||
// Tool, axes & bounds
|
||||
this.toolView = this.draw_tool(scene, bbox);
|
||||
this.axesView = this.draw_axes(scene, bbox);
|
||||
this.bboxView = this.draw_bbox(scene, bbox);
|
||||
this.envelopeView = this.draw_envelope(scene);
|
||||
},
|
||||
|
||||
|
||||
render: function () {
|
||||
window.requestAnimationFrame(this.render);
|
||||
if (typeof this.scene == 'undefined') return;
|
||||
|
||||
if (this.controls.update() || this.dirty) {
|
||||
this.dirty = false;
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
get_model_bounds: function () {
|
||||
var bbox = new THREE.Box3(new THREE.Vector3(0, 0, 0),
|
||||
new THREE.Vector3(0.00001, 0.00001, 0.00001));
|
||||
|
||||
function add(o) {
|
||||
if (typeof o != 'undefined') {
|
||||
var oBBox = new THREE.Box3();
|
||||
oBBox.setFromObject(o);
|
||||
bbox.union(oBBox);
|
||||
}
|
||||
}
|
||||
|
||||
add(this.pathView);
|
||||
add(this.surfaceMesh);
|
||||
add(this.workpieceMesh);
|
||||
|
||||
return bbox;
|
||||
},
|
||||
|
||||
|
||||
snap: function (view) {
|
||||
if (this.loading) return;
|
||||
if (view != this.snapView) {
|
||||
this.snapView = view;
|
||||
cookie.set('snap-view', view);
|
||||
}
|
||||
|
||||
var bbox = this.get_model_bounds();
|
||||
this.controls.reset();
|
||||
bbox.getCenter(this.controls.target);
|
||||
this.update_view();
|
||||
|
||||
// Compute new camera position
|
||||
var center = bbox.getCenter(new THREE.Vector3());
|
||||
var offset = new THREE.Vector3();
|
||||
|
||||
if (view == 'isometric') {offset.y -= 1; offset.z += 1;}
|
||||
if (view == 'front') offset.y -= 1;
|
||||
if (view == 'back') offset.y += 1;
|
||||
if (view == 'left') offset.x -= 1;
|
||||
if (view == 'right') offset.x += 1;
|
||||
if (view == 'top') offset.z += 1;
|
||||
if (view == 'bottom') offset.z -= 1;
|
||||
offset.normalize();
|
||||
|
||||
// Initial camera position
|
||||
var position = new THREE.Vector3().copy(center).add(offset);
|
||||
this.camera.position.copy(position);
|
||||
this.camera.lookAt(center); // Get correct camera orientation
|
||||
|
||||
var theta = this.camera.fov / 180 * Math.PI; // View angle
|
||||
var cameraLine = new THREE.Line3(center, position);
|
||||
var cameraUp = new THREE.Vector3().copy(this.camera.up)
|
||||
.applyQuaternion(this.camera.quaternion);
|
||||
var cameraLeft =
|
||||
new THREE.Vector3().copy(offset).cross(cameraUp).normalize();
|
||||
|
||||
var corners = [
|
||||
new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z),
|
||||
new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.max.z),
|
||||
new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.min.z),
|
||||
new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.max.z),
|
||||
new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.min.z),
|
||||
new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.z),
|
||||
new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.min.z),
|
||||
new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.max.z),
|
||||
]
|
||||
|
||||
var dist = this.camera.near; // Min camera dist
|
||||
|
||||
for (var i = 0; i < corners.length; i++) {
|
||||
// Project on to camera line
|
||||
var p1 = cameraLine
|
||||
.closestPointToPoint(corners[i], false, new THREE.Vector3());
|
||||
|
||||
// Compute distance from projection to center
|
||||
var d = p1.distanceTo(center);
|
||||
if (cameraLine.closestPointToPointParameter(p1, false) < 0) d = -d;
|
||||
|
||||
// Compute up line
|
||||
var up =
|
||||
new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraUp));
|
||||
|
||||
// Project on to up line
|
||||
var p2 = up.closestPointToPoint(corners[i], false, new THREE.Vector3());
|
||||
|
||||
// Compute length
|
||||
var l = p1.distanceTo(p2);
|
||||
|
||||
// Update min camera distance
|
||||
dist = Math.max(dist, d + l / Math.tan(theta / 2));
|
||||
|
||||
// Compute left line
|
||||
var left =
|
||||
new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraLeft));
|
||||
|
||||
// Project on to left line
|
||||
var p3 =
|
||||
left.closestPointToPoint(corners[i], false, new THREE.Vector3());
|
||||
|
||||
// Compute length
|
||||
l = p1.distanceTo(p3);
|
||||
|
||||
// Update min camera distance
|
||||
dist = Math.max(dist, d + l / Math.tan(theta / 2) / this.camera.aspect);
|
||||
}
|
||||
|
||||
this.camera.position.copy(offset.multiplyScalar(dist * 1.2).add(center));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mixins: [require('./axis-vars')]
|
||||
}
|
||||
42
src/js/settings-view.js
Normal file
42
src/js/settings-view.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#settings-view-template',
|
||||
props: ['config', 'template'],
|
||||
|
||||
|
||||
events: {
|
||||
'input-changed': function() {
|
||||
this.$dispatch('config-changed');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/js/sock.js
Normal file
127
src/js/sock.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
var Sock = function (url, retry, timeout) {
|
||||
if (!(this instanceof Sock)) return new Sock(url, retry);
|
||||
|
||||
if (typeof retry == 'undefined') retry = 2000;
|
||||
if (typeof timeout == 'undefined') timeout = 16000;
|
||||
|
||||
this.url = url;
|
||||
this.retry = retry;
|
||||
this.timeout = timeout;
|
||||
this.divisions = 4;
|
||||
this.count = 0;
|
||||
|
||||
this.connect();
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype.onmessage = function () {}
|
||||
Sock.prototype.onopen = function () {}
|
||||
Sock.prototype.onclose = function () {}
|
||||
|
||||
|
||||
Sock.prototype.connect = function () {
|
||||
console.debug('connecting to', this.url);
|
||||
this.close();
|
||||
|
||||
this._sock = new SockJS(this.url);
|
||||
|
||||
this._sock.onmessage = function (e) {
|
||||
console.debug('msg:', e.data);
|
||||
this.heartbeat('msg');
|
||||
this.onmessage(e);
|
||||
}.bind(this);
|
||||
|
||||
|
||||
this._sock.onopen = function () {
|
||||
console.debug('connected');
|
||||
this.heartbeat('open');
|
||||
this.onopen();
|
||||
}.bind(this);
|
||||
|
||||
|
||||
this._sock.onclose = function () {
|
||||
console.debug('disconnected');
|
||||
this._cancel_timeout();
|
||||
|
||||
this.onclose();
|
||||
if (typeof this._sock != 'undefined')
|
||||
setTimeout(this.connect.bind(this), this.retry);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype._timedout = function () {
|
||||
// Divide timeout so slow browser doesn't trigger timeouts when the
|
||||
// connection is good.
|
||||
if (this.divisions <= ++this.count) {
|
||||
console.debug('connection timedout');
|
||||
this._timeout = undefined;
|
||||
this._sock.close();
|
||||
|
||||
} else this._set_timeout();
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype._cancel_timeout = function () {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = undefined;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype._set_timeout = function () {
|
||||
this._timeout = setTimeout(this._timedout.bind(this),
|
||||
this.timeout / this.divisions);
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype.heartbeat = function (msg) {
|
||||
//console.debug('heartbeat ' + new Date().toLocaleTimeString() + ' ' + msg);
|
||||
this._cancel_timeout();
|
||||
this._set_timeout();
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype.close = function () {
|
||||
if (typeof this._sock != 'undefined') {
|
||||
var sock = this._sock;
|
||||
this._sock = undefined;
|
||||
sock.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Sock.prototype.send = function (msg) {this._sock.send(msg)}
|
||||
|
||||
|
||||
module.exports = Sock
|
||||
89
src/js/templated-input.js
Normal file
89
src/js/templated-input.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
replace: true,
|
||||
template: '#templated-input-template',
|
||||
props: ['name', 'model', 'template'],
|
||||
|
||||
|
||||
data: function () {return {view: ''}},
|
||||
|
||||
|
||||
computed: {
|
||||
metric: function () {return this.$root.metric()},
|
||||
|
||||
|
||||
_view: function () {
|
||||
if (this.template.scale) {
|
||||
if (this.metric) return 1 * this.model.toFixed(3);
|
||||
|
||||
return 1 * (this.model / this.template.scale).toFixed(4);
|
||||
}
|
||||
|
||||
return this.model;
|
||||
},
|
||||
|
||||
|
||||
units: function () {
|
||||
return (this.metric || !this.template.iunit) ?
|
||||
this.template.unit : this.template.iunit;
|
||||
},
|
||||
|
||||
|
||||
title: function () {
|
||||
var s = 'Default ' + this.template.default + ' ' +
|
||||
(this.template.unit || '');
|
||||
if (typeof this.template.help != 'undefined')
|
||||
s = this.template.help + '\n' + s;
|
||||
return s;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
_view: function () {this.view = this._view},
|
||||
|
||||
|
||||
view: function () {
|
||||
if (this.template.scale && !this.metric)
|
||||
this.model = this.view * this.template.scale;
|
||||
else this.model = this.view;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {this.view = this._view},
|
||||
|
||||
|
||||
methods: {
|
||||
change: function () {this.$dispatch('input-changed')}
|
||||
}
|
||||
}
|
||||
152
src/js/tool-view.js
Normal file
152
src/js/tool-view.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict';
|
||||
|
||||
var api = require('./api');
|
||||
var modbus = require('./modbus.js');
|
||||
|
||||
|
||||
module.exports = {
|
||||
template: '#tool-view-template',
|
||||
props: ['config', 'template', 'state'],
|
||||
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
address: 0,
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
components: {'modbus-reg': require('./modbus-reg.js')},
|
||||
|
||||
|
||||
watch: {
|
||||
'state.mr': function () {this.value = this.state.mr}
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
'input-changed': function() {
|
||||
this.$dispatch('config-changed');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {this.value = this.state.mr},
|
||||
|
||||
|
||||
computed: {
|
||||
regs_tmpl: function () {return this.template['modbus-spindle'].regs},
|
||||
tool_type: function () {return this.config.tool['tool-type'].toUpperCase()},
|
||||
|
||||
|
||||
is_modbus: function () {
|
||||
return this.tool_type != 'DISABLED' && this.tool_type != 'PWM SPINDLE'
|
||||
},
|
||||
|
||||
|
||||
modbus_status: function () {return modbus.status_to_string(this.state.mx)}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
get_reg_type: function (reg) {
|
||||
return this.regs_tmpl.template['reg-type'].values[this.state[reg + 'vt']]
|
||||
},
|
||||
|
||||
|
||||
get_reg_addr: function (reg) {return this.state[reg + 'va']},
|
||||
get_reg_value: function (reg) {return this.state[reg + 'vv']},
|
||||
|
||||
|
||||
get_reg_fails: function (reg) {
|
||||
var fails = this.state[reg + 'vr']
|
||||
return fails == 255 ? 'Max' : fails;
|
||||
},
|
||||
|
||||
|
||||
show_modbus_field: function (key) {
|
||||
return key != 'regs' &&
|
||||
(key != 'multi-write' || this.tool_type == 'CUSTOM MODBUS VFD');
|
||||
},
|
||||
|
||||
|
||||
read: function (e) {
|
||||
e.preventDefault();
|
||||
api.put('modbus/read', {address: this.address});
|
||||
},
|
||||
|
||||
|
||||
write: function (e) {
|
||||
e.preventDefault();
|
||||
api.put('modbus/write', {address: this.address, value: this.value});
|
||||
},
|
||||
|
||||
|
||||
customize: function (e) {
|
||||
e.preventDefault();
|
||||
this.config.tool['tool-type'] = 'Custom Modbus VFD';
|
||||
|
||||
var regs = this.config['modbus-spindle'].regs;
|
||||
for (var i = 0; i < regs.length; i++) {
|
||||
var reg = this.regs_tmpl.index[i];
|
||||
regs[i]['reg-type'] = this.get_reg_type(reg);
|
||||
regs[i]['reg-addr'] = this.get_reg_addr(reg);
|
||||
regs[i]['reg-value'] = this.get_reg_value(reg);
|
||||
}
|
||||
|
||||
this.$dispatch('config-changed');
|
||||
},
|
||||
|
||||
|
||||
clear: function (e) {
|
||||
e.preventDefault();
|
||||
this.config.tool['tool-type'] = 'Custom Modbus VFD';
|
||||
|
||||
var regs = this.config['modbus-spindle'].regs;
|
||||
for (var i = 0; i < regs.length; i++) {
|
||||
regs[i]['reg-type'] = 'disabled';
|
||||
regs[i]['reg-addr'] = 0;
|
||||
regs[i]['reg-value'] = 0;
|
||||
}
|
||||
|
||||
this.$dispatch('config-changed');
|
||||
},
|
||||
|
||||
|
||||
reset_failures: function (e) {
|
||||
e.preventDefault();
|
||||
var regs = this.config['modbus-spindle'].regs;
|
||||
for (var reg = 0; reg < regs.length; reg++)
|
||||
this.$dispatch('send', '\$' + reg + 'vr=0');
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/js/unit-value.js
Normal file
58
src/js/unit-value.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/******************************************************************************\
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
module.exports = {
|
||||
replace: true,
|
||||
template: '{{text}}<span class="unit">{{metric ? unit : iunit}}</span>',
|
||||
props: ['value', 'precision', 'unit', 'iunit', 'scale'],
|
||||
|
||||
|
||||
computed: {
|
||||
metric: function () {return !this.$root.state.imperial},
|
||||
|
||||
|
||||
text: function () {
|
||||
var value = this.value;
|
||||
if (typeof value == 'undefined') return '';
|
||||
|
||||
if (!this.metric) value /= this.scale;
|
||||
|
||||
return (1 * value.toFixed(this.precision)).toLocaleString();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
ready: function () {
|
||||
if (typeof this.precision == 'undefined') this.precision = 0;
|
||||
if (typeof this.unit == 'undefined') this.unit = 'mm';
|
||||
if (typeof this.iunit == 'undefined') this.iunit = 'in';
|
||||
if (typeof this.scale == 'undefined') this.scale = 25.4;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user